10 Commits

Author SHA1 Message Date
ce763be7e9 add statistics for bottle and sleep events 2025-11-21 23:06:49 +01:00
f8f5d68bb6 MainActivity: show save button if any values has changed 2025-11-21 21:58:00 +01:00
280d4558ad MainActivity: use unique templates for notes 2025-11-21 21:57:55 +01:00
d74310fad5 LunaEvent: add sleep event 2025-11-21 09:13:33 +01:00
9f8f277d75 MainActivity: allow amount for poo and pee events
An unspecified amount has also been added
to have the same semantics as before.

During these actions, the strings for title
and description of dialogs have been cleaned up.
2025-11-19 20:37:10 +01:00
742c5515b1 strings: rename scale to weight in identifiers 2025-11-19 20:37:10 +01:00
d49701a488 LunaEvent: remove quantity when the value is invalid 2025-11-19 20:37:10 +01:00
fb6edf981b notes: add icons to use previous/next event as template 2025-11-19 20:37:10 +01:00
52b23151d6 MainActivity: preset quantity of bottle, weight and temperature
Use the quantity of previous events
to initialize new events.
2025-11-19 20:37:10 +01:00
23372408d5 events: allow editing of all used values
1. Allow to change the date/time and
other relevant values of an event
on creation and after it was created.

2. Harmonize layout file names and
variable names.
2025-11-19 20:37:01 +01:00
22 changed files with 907 additions and 81 deletions

View File

@@ -60,4 +60,5 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation(libs.mpandroidchart.vv310)
}

View File

@@ -30,6 +30,10 @@
android:name=".SettingsActivity"
android:label="@string/settings_title"
android:theme="@style/Theme.LunaTracker"/>
<activity
android:name=".StatisticsActivity"
android:label="@string/statistics_title"
android:theme="@style/Theme.LunaTracker"/>
</application>
</manifest>

View File

@@ -6,12 +6,14 @@ import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.text.Editable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.NumberPicker
import android.widget.PopupWindow
@@ -49,6 +51,8 @@ class MainActivity : AppCompatActivity() {
const val TAG = "MainActivity"
const val UPDATE_EVERY_SECS: Long = 30
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
// list of all events
var allEvents = arrayListOf<LunaEvent>()
}
var logbook: Logbook? = null
@@ -85,7 +89,7 @@ class MainActivity : AppCompatActivity() {
showAddLogbookDialog(true)
}
findViewById<View>(R.id.button_bottle).setOnClickListener {
addBabyBottleEvent()
addBabyBottleEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE))
}
findViewById<View>(R.id.button_food).setOnClickListener {
addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD))
@@ -144,7 +148,8 @@ class MainActivity : AppCompatActivity() {
if (logbook == null)
Log.w(TAG, "showLogbook(): logbook is null!")
setListAdapter(logbook?.logs ?: arrayListOf())
allEvents = logbook?.logs ?: arrayListOf()
setListAdapter(allEvents)
}
override fun onStart() {
@@ -191,12 +196,8 @@ class MainActivity : AppCompatActivity() {
super.onStop()
}
fun getAllEvents(): ArrayList<LunaEvent> {
return logbook?.logs ?: arrayListOf()
}
fun addBabyBottleEvent() {
val event = LunaEvent(LunaEvent.TYPE_BABY_BOTTLE)
fun addBabyBottleEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askBabyBottleContent(event, true) {
saveEvent(event)
}
@@ -239,6 +240,7 @@ class MainActivity : AppCompatActivity() {
}
fun addWeightEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askWeightValue(event, true) { saveEvent(event) }
}
@@ -282,6 +284,7 @@ class MainActivity : AppCompatActivity() {
}
fun addTemperatureEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askTemperatureValue(event, true) { saveEvent(event) }
}
@@ -328,7 +331,7 @@ class MainActivity : AppCompatActivity() {
alertDialog.show()
}
fun datePickerHelper(time: Long, dateTextView: TextView): Calendar {
fun datePickerHelper(time: Long, dateTextView: TextView, onChange: (Long) -> Unit = {}): Calendar {
dateTextView.text = DateUtils.formatDateTime(time)
val dateTime = Calendar.getInstance()
@@ -347,6 +350,7 @@ class MainActivity : AppCompatActivity() {
{ _, hour, minute ->
dateTime.set(year, month, day, hour, minute)
dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000)
onChange.invoke(dateTime.time.time / 1000)
},
startHour,
startMinute,
@@ -359,7 +363,7 @@ class MainActivity : AppCompatActivity() {
}
fun saveEvent(event: LunaEvent) {
if (!getAllEvents().contains(event)) {
if (!allEvents.contains(event)) {
// new event
logEvent(event)
}
@@ -369,6 +373,93 @@ class MainActivity : AppCompatActivity() {
saveLogbook()
}
fun addSleepEvent(event: LunaEvent) {
askSleepValue(event) { saveEvent(event) }
}
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val 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 durationMinus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_minus5)
val durationPlus5Button = dialogView.findViewById<Button>(R.id.dialog_date_duration_plus5)
val currentDurationTextColor = durationTextView.currentTextColor
val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger)
var duration = event.quantity
fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean {
val now = Calendar.getInstance().time.time / 1000
return (timeSeconds + durationSeconds) <= now && durationSeconds < (12 * 60 * 60)
}
val onDateChange = { time: Long ->
durationTextView.setTextColor(currentDurationTextColor)
if (duration == 0) {
// baby is sleeping
durationTextView.text = "💤"
} else {
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration.toLong())
if (!isValidTime(time, duration)) {
durationTextView.setTextColor(invalidDurationTextColor)
}
}
}
val pickedDateTime = datePickerHelper(event.time, datePicker, onDateChange)
onDateChange(pickedDateTime.time.time / 1000)
fun adjust(minutes: Int) {
duration += minutes * 60
if (duration < 0) {
duration = 0
}
onDateChange(pickedDateTime.time.time / 1000)
}
durationMinus5Button.setOnClickListener { adjust(-5) }
durationPlus5Button.setOnClickListener { adjust(5) }
durationNowButton.setOnClickListener {
val now = Calendar.getInstance().time.time / 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 ->
val time = pickedDateTime.time.time / 1000
if (isValidTime(time, duration)) {
event.time = time
event.quantity = duration
onPositive()
} else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
}
dialogInterface.dismiss()
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
}
fun addAmountEvent(event: LunaEvent) {
askAmountValue(event, true) { saveEvent(event) }
}
@@ -457,13 +548,59 @@ class MainActivity : AppCompatActivity() {
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
val dateTV = dialogView.findViewById<TextView>(R.id.notes_date_picker)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
val nextTextView = dialogView.findViewById<TextView>(R.id.notes_template_next)
val prevTextView = dialogView.findViewById<TextView>(R.id.notes_template_prev)
val templates = allEvents.filter { it.type == event.type }.distinctBy { it.notes }.sortedBy { it.time }
fun updateContent(current: LunaEvent) {
val prevEvent = getPreviousSameEvent(current, templates)
var nextEvent = getNextSameEvent(current, templates)
notesET.setText(current.notes)
if (useQuantity) {
qtyET.setText(current.quantity.toString())
}
if (nextEvent == null && current != event) {
nextEvent = event
}
if (nextEvent != null) {
nextTextView.setOnClickListener {
notesET.setText(nextEvent.notes)
if (useQuantity) {
qtyET.setText(nextEvent.quantity.toString())
}
updateContent(nextEvent)
}
nextTextView.alpha = 1.0f
} else {
nextTextView.setOnClickListener {}
nextTextView.alpha = 0.5f
}
if (prevEvent != null) {
prevTextView.setOnClickListener {
notesET.setText(prevEvent.notes)
if (useQuantity) {
qtyET.setText(prevEvent.quantity.toString())
}
updateContent(prevEvent)
}
prevTextView.alpha = 1.0f
} else {
prevTextView.setOnClickListener {}
prevTextView.alpha = 0.5f
}
}
notesET.setText(event.notes)
if (useQuantity) {
@@ -472,8 +609,10 @@ class MainActivity : AppCompatActivity() {
qtyET.visibility = View.GONE
}
updateContent(event)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val notes = notesET.text.toString()
val notes = notesET.text.toString().trim()
if (useQuantity) {
val quantity = qtyET.text.toString().toIntOrNull()
@@ -483,7 +622,7 @@ class MainActivity : AppCompatActivity() {
event.quantity = quantity
onPositive()
} else {
Toast.makeText(applicationContext, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
}
} else {
@@ -523,7 +662,14 @@ class MainActivity : AppCompatActivity() {
alertDialog.show()
}
fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
fun setToPreviousQuantity(event: LunaEvent) {
val prev = getPreviousSameEvent(event, allEvents)
if (prev != null) {
event.quantity = prev.quantity
}
}
fun getPreviousSameEvent(event: LunaEvent, items: List<LunaEvent>): LunaEvent? {
var previousEvent: LunaEvent? = null
for (item in items) {
if (item.type == event.type && item.time < event.time) {
@@ -537,7 +683,7 @@ class MainActivity : AppCompatActivity() {
return previousEvent
}
fun getNextSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
fun getNextSameEvent(event: LunaEvent, items: List<LunaEvent>): LunaEvent? {
var nextEvent: LunaEvent? = null
for (item in items) {
if (item.type == event.type && item.time > event.time) {
@@ -554,6 +700,12 @@ class MainActivity : AppCompatActivity() {
fun showEventDetailDialog(originalEvent: LunaEvent) {
val event = LunaEvent(originalEvent)
fun eventValuesChanged(): Boolean {
return (event.time != originalEvent.time
|| event.quantity != originalEvent.quantity
|| event.notes != originalEvent.notes)
}
// Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
pauseLogbookUpdate = true
@@ -564,39 +716,13 @@ class MainActivity : AppCompatActivity() {
val emojiTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji)
val descriptionTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description)
val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
val dateEndTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date_end)
val quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
val notesTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes)
emojiTextView.text = event.getTypeEmoji(this)
descriptionTextView.text = event.getTypeDescription(this)
val pickedTime = datePickerHelper(event.time, dateTextView)
val updateValues = {
quantityTextView.text = NumericUtils(this).formatEventQuantity(event)
notesTextView.text = event.notes
}
updateValues()
quantityTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.TYPE_WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
}
}
notesTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_FOOD,
LunaEvent.TYPE_MEDICINE,
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
}
}
d.setView(dialogView)
d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i ->
@@ -604,12 +730,8 @@ class MainActivity : AppCompatActivity() {
dialogInterface.dismiss()
}
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i ->
event.time = pickedTime.time.time / 1000
if (event.time != originalEvent.time
|| event.quantity != originalEvent.quantity
|| event.notes != originalEvent.notes) {
d.setNegativeButton(R.string.dialog_event_detail_save_button) { dialogInterface, i ->
if (eventValuesChanged()) {
originalEvent.time = event.time
originalEvent.quantity = event.quantity
originalEvent.notes = event.notes
@@ -619,6 +741,10 @@ class MainActivity : AppCompatActivity() {
dialogInterface.dismiss()
}
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
@@ -631,6 +757,50 @@ class MainActivity : AppCompatActivity() {
pauseLogbookUpdate = false
}
val updateValues = {
quantityTextView.text = NumericUtils(this).formatEventQuantity(event)
notesTextView.text = event.notes
if (event.type == LunaEvent.TYPE_SLEEP && event.quantity > 0) {
dateEndTextView.text = DateUtils.formatDateTime(event.time + event.quantity)
dateEndTextView.visibility = View.VISIBLE
} else {
dateEndTextView.visibility = View.GONE
}
alertDialog.getButton(DialogInterface.BUTTON_NEGATIVE).visibility = if (eventValuesChanged()) {
View.VISIBLE
} else {
View.GONE
}
}
updateValues()
datePickerHelper(event.time, dateTextView, { newTime: Long ->
event.time = newTime
updateValues()
})
quantityTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.TYPE_WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
LunaEvent.TYPE_SLEEP -> askSleepValue(event, updateValues)
}
}
notesTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_FOOD,
LunaEvent.TYPE_MEDICINE,
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
}
}
// show optional signature
if (event.signature.isNotEmpty()) {
val signatureTextEdit = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature)
@@ -638,8 +808,6 @@ class MainActivity : AppCompatActivity() {
signatureTextEdit.visibility = View.VISIBLE
}
val allEvents = getAllEvents()
// create link to prevent event of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
val previousEvent = getPreviousSameEvent(event, allEvents)
@@ -772,7 +940,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({
setLoading(false)
loadLogbookList()
Toast.makeText(applicationContext, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
})
}
@@ -929,7 +1097,7 @@ class MainActivity : AppCompatActivity() {
setLoading(false)
Toast.makeText(
applicationContext,
this@MainActivity,
if (lastEventAdded != null)
R.string.toast_event_added
else
@@ -991,7 +1159,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({
setLoading(false)
Toast.makeText(applicationContext, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
recyclerView.adapter?.notifyDataSetChanged()
savingEvent(false)
})
@@ -1043,6 +1211,10 @@ class MainActivity : AppCompatActivity() {
addAmountEvent(LunaEvent(LunaEvent.TYPE_PUKE))
dismiss()
}
contentView.findViewById<View>(R.id.button_sleep).setOnClickListener {
addSleepEvent(LunaEvent(LunaEvent.TYPE_SLEEP))
dismiss()
}
contentView.findViewById<View>(R.id.button_colic).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_COLIC))
dismiss()
@@ -1055,6 +1227,16 @@ class MainActivity : AppCompatActivity() {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BATH))
dismiss()
}
contentView.findViewById<View>(R.id.button_statistics).setOnClickListener {
if (logbook != null && !pauseLogbookUpdate) {
val i = Intent(applicationContext, StatisticsActivity::class.java)
i.putExtra("LOOGBOOK_NAME", logbook!!.name)
startActivity(i)
} else {
Toast.makeText(applicationContext, "No logbook selected!", Toast.LENGTH_SHORT).show()
}
dismiss()
}
}.also { popupWindow ->
popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({

View File

@@ -0,0 +1,375 @@
package it.danieleverducci.lunatracker
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Spinner
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.github.mikephil.charting.charts.BarChart
import com.github.mikephil.charting.data.BarData
import com.github.mikephil.charting.data.BarDataSet
import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet
import it.danieleverducci.lunatracker.entities.LunaEvent
import utils.DateUtils.Companion.formatTimeDuration
import utils.NumericUtils
import java.util.Calendar
import java.util.Date
import kotlin.math.max
import kotlin.math.min
class StatisticsActivity : AppCompatActivity() {
lateinit var barChart: BarChart
lateinit var noDataTextView: TextView
lateinit var eventTypeSelection: Spinner
lateinit var dataTypeSelection: Spinner
lateinit var timeRangeSelection: Spinner
// default selection
var eventTypeSelectionValue = "BOTTLE"
var dataTypeSelectionValue = "AMOUNT"
var timeRangeSelectionValue = "DAY"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_statistics)
val logbookName = intent.getStringExtra("LOOGBOOK_NAME")
if (logbookName == null) {
finish()
return
}
noDataTextView = findViewById(R.id.no_data)
barChart = findViewById(R.id.bar_chart)
barChart.setBackgroundColor(Color.WHITE)
barChart.description.text = "$logbookName"
barChart.setDrawValueAboveBar(false)
barChart.axisLeft.setAxisMinimum(0F)
barChart.axisLeft.setDrawGridLines(false)
barChart.axisLeft.setDrawLabels(false)
barChart.axisRight.setDrawGridLines(false)
barChart.axisRight.setDrawLabels(false)
barChart.xAxis.setDrawGridLines(true)
barChart.xAxis.setDrawLabels(true)
barChart.xAxis.setDrawAxisLine(false)
eventTypeSelection = findViewById(R.id.type_selection)
dataTypeSelection = findViewById(R.id.data_selection)
timeRangeSelection = findViewById(R.id.time_selection)
setupSpinner(eventTypeSelectionValue,
R.id.type_selection,
R.array.StatisticsTypeLabels,
R.array.StatisticsTypeValues,
object : SpinnerItemSelected {
override fun call(newValue: String?) {
if (newValue != null) {
eventTypeSelectionValue = newValue
//Log.d("event", "new value: $newValue")
updateGraph()
}
}
}
)
setupSpinner(dataTypeSelectionValue,
R.id.data_selection,
R.array.StatisticsDataLabels,
R.array.StatisticsDataValues,
object : SpinnerItemSelected {
override fun call(newValue: String?) {
if (newValue != null) {
dataTypeSelectionValue = newValue
//Log.d("event", "new value: $newValue")
updateGraph()
}
}
}
)
setupSpinner(timeRangeSelectionValue,
R.id.time_selection,
R.array.StatisticsTimeLabels,
R.array.StatisticsTimeValues,
object : SpinnerItemSelected {
override fun call(newValue: String?) {
if (newValue != null) {
timeRangeSelectionValue = newValue
//Log.d("event", "new value: $newValue")
updateGraph()
}
}
}
)
updateGraph()
}
fun updateGraph() {
val eventType = when (eventTypeSelectionValue) {
"BOTTLE" -> LunaEvent.TYPE_BABY_BOTTLE
"SLEEP" -> LunaEvent.TYPE_SLEEP
else -> {
Log.e(TAG, "unhandled eventTypeSelectionValue: $eventTypeSelectionValue")
return
}
}
val allEvents = MainActivity.allEvents.filter { it.type == eventType }.sortedBy { it.time }
val values = ArrayList<BarEntry>()
val labels = ArrayList<String>()
val unixToSpan = when (timeRangeSelectionValue) {
"DAY" -> { unix: Long -> unixToDays(unix) }
"WEEK" -> { unix: Long -> unixToWeeks(unix) }
"MONTH" -> { unix: Long -> unixToMonths(unix) }
else -> {
Log.e(TAG, "Invalid timeRangeSelectionValue: $timeRangeSelectionValue")
return
}
}
val spanToUnix = when (timeRangeSelectionValue) {
"DAY" -> { span: Int -> daysToUnix(span) }
"WEEK" -> { span: Int -> weeksToUnix(span) }
"MONTH" -> { span: Int -> monthsToUnix(span) }
else -> {
Log.e(TAG, "Invalid timeRangeSelectionValue: $timeRangeSelectionValue")
return
}
}
fun spanToLabel(span: Int): String {
val dateTime = Calendar.getInstance()
dateTime.time = Date(1000 * spanToUnix(span))
val year = dateTime.get(Calendar.YEAR)
val month = dateTime.get(Calendar.MONTH) + 1 // month starts at 0
val week = dateTime.get(Calendar.WEEK_OF_YEAR)
val day = dateTime.get(Calendar.DAY_OF_MONTH)
return when (timeRangeSelectionValue) {
"DAY" -> "$day/$month/$year"
"WEEK" -> "$week/$year"
"MONTH" -> "$month/$year"
else -> {
Log.e(TAG, "Invalid timeRangeSelectionValue: $timeRangeSelectionValue")
"?"
}
}
}
if (allEvents.isNotEmpty()) {
barChart.visibility = View.VISIBLE
noDataTextView.visibility = View.GONE
// unix time span of all events
val startUnix = allEvents.minOf { it.getStartTime() }
val endUnix = allEvents.maxOf { it.getEndTime() }
// convert to days, weeks or months
val startSpan = unixToSpan(startUnix)
val endSpan = unixToSpan(endUnix)
//Log.d(TAG, "startUnix: $startUnix (${Date(1000 * startUnix)}), startSpan: $startSpan (${Date(1000 * spanToUnix(startSpan))}), endUnix: $endUnix (${Date(1000 * endUnix)}), endSpan: $endSpan (${Date(1000 * spanToUnix(endSpan))})")
for (span in startSpan..endSpan) {
values.add(BarEntry(values.size.toFloat(), 0F))
labels.add(spanToLabel(span))
}
for (event in allEvents) {
if (dataTypeSelectionValue == "AMOUNT") {
if (eventTypeSelectionValue == "SLEEP") {
// a sleep event can span to another day
// distribute sleep time over the days
val startUnix = event.getStartTime()
val endUnix = event.getEndTime()
val begIndex = unixToSpan(startUnix)
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) {
val spanBegin = spanToUnix(i)
val spanEnd = spanToUnix(i + 1)
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, endUnix: ${Date(endUnix * 1000)}")
val beg = max(mid, spanBegin)
val end = min(endUnix, spanEnd)
val index = i - startSpan
val duration = end - beg
//Log.d(TAG, "[$index] beg: ${Date(beg * 1000)}, end: ${Date(end * 1000)}, ${formatTimeDuration(this, duration)}")
values[index].y += duration
mid = end
}
} else {
val index = unixToSpan(event.time) - startSpan
//Log.d(TAG, "[${index}] ${event.quantity}")
values[index].y += event.quantity
}
} else {
val index = unixToSpan(event.time) - startSpan
values[index].y += 1
}
}
} else {
barChart.visibility = View.GONE
noDataTextView.visibility = View.VISIBLE
}
barChart.xAxis.setLabelCount(min(values.size, 24))
val set1 = BarDataSet(values, "")
set1.setDrawValues(true)
set1.setDrawIcons(false)
val dataSets = ArrayList<IBarDataSet?>()
dataSets.add(set1)
val data = BarData(dataSets)
data.setValueFormatter(object : ValueFormatter() {
override fun getFormattedValue(value: Float): String {
//Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}")
return when (dataTypeSelectionValue) {
"EVENT" -> value.toInt().toString()
"AMOUNT" -> when (eventTypeSelectionValue) {
"BOTTLE" -> NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_BABY_BOTTLE, value.toInt())
"SLEEP" -> NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_SLEEP, value.toInt())
else -> {
Log.e(TAG, "unhandled eventTypeSelectionValue: $eventTypeSelectionValue")
value.toInt().toString()
}
} else -> {
Log.e(TAG, "unhandled dataTypeSelectionValue: $dataTypeSelectionValue")
value.toInt().toString()
}
}
}
})
barChart.xAxis.valueFormatter = object: ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return labels.getOrElse(value.toInt(), {"?"})
}
}
data.setValueTextSize(12f)
barChart.setData(data)
barChart.invalidate()
}
private interface SpinnerItemSelected {
fun call(newValue: String?)
}
private fun setupSpinner(
currentValue: String,
spinnerId: Int,
arrayId: Int,
arrayValuesId: Int,
callback: SpinnerItemSelected
) {
val arrayValues = resources.getStringArray(arrayValuesId)
val spinner = findViewById<Spinner>(spinnerId)
val spinnerAdapter =
ArrayAdapter.createFromResource(this, arrayId, R.layout.statistics_spinner_item) //android.R.layout.simple_spinner_item) //android.R.layout.simple_list_item_single_choice) //R.layout.spinner_item_settings)
spinner.adapter = spinnerAdapter
spinner.setSelection(arrayValues.indexOf(currentValue))
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
var check = 0
override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
if (pos >= arrayValues.size) {
Toast.makeText(
this@StatisticsActivity,
"pos out of bounds: $arrayValues", Toast.LENGTH_SHORT
).show()
return
}
if (check++ > 0) {
callback.call(arrayValues[pos])
}
}
override fun onNothingSelected(parent: AdapterView<*>?) {
// ignore
}
}
}
companion object {
const val TAG = "StatisticsActivity"
fun unixToMonths(seconds: Long): Int {
val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR)
val months = dateTime.get(Calendar.MONTH)
return 12 * years + months
}
fun monthsToUnix(months: Int): Long {
val dateTime = Calendar.getInstance()
dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, months / 12)
dateTime.set(Calendar.MONTH, months % 12)
//Calendar.MONTH_OF_YEAR?
dateTime.set(Calendar.HOUR, 0)
dateTime.set(Calendar.MINUTE, 0)
dateTime.set(Calendar.SECOND, 0)
return dateTime.time.time / 1000
}
fun unixToWeeks(seconds: Long): Int {
val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR)
val weeks = dateTime.get(Calendar.WEEK_OF_YEAR)
return 52 * years + weeks
}
fun weeksToUnix(weeks: Int): Long {
val dateTime = Calendar.getInstance()
dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, weeks / 52)
dateTime.set(Calendar.WEEK_OF_YEAR, weeks % 52)
dateTime.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
dateTime.set(Calendar.HOUR, 0)
dateTime.set(Calendar.MINUTE, 0)
dateTime.set(Calendar.SECOND, 0)
return dateTime.time.time / 1000
}
fun unixToDays(seconds: Long): Int {
val dateTime = Calendar.getInstance()
dateTime.time = Date(seconds * 1000)
val years = dateTime.get(Calendar.YEAR)
val days = dateTime.get(Calendar.DAY_OF_YEAR)
return 365 * years + days
}
// convert from days to Date
fun daysToUnix(days: Int): Long {
val dateTime = Calendar.getInstance()
dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, days / 365)
dateTime.set(Calendar.DAY_OF_YEAR, days % 365)
dateTime.set(Calendar.HOUR, 0)
dateTime.set(Calendar.MINUTE, 0)
dateTime.set(Calendar.SECOND, 0)
return dateTime.time.time / 1000
}
}
}

View File

@@ -59,7 +59,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
LunaEvent.TYPE_CUSTOM -> item.notes
else -> item.getTypeDescription(context)
}
holder.time.text = DateUtils.formatTimeAgo(context, item.time)
holder.time.text = DateUtils.formatTimeAgo(context, item.getEndTime())
var quantityText = numericUtils.formatEventQuantity(item)
// if the event is weight, show difference with the last one

View File

@@ -30,6 +30,7 @@ class LunaEvent: Comparable<LunaEvent> {
const val TYPE_FOOD = "FOOD"
const val TYPE_PUKE = "PUKE"
const val TYPE_BATH = "BATH"
const val TYPE_SLEEP = "SLEEP"
}
private val jo: JSONObject
@@ -45,10 +46,12 @@ class LunaEvent: Comparable<LunaEvent> {
jo.put("type", value)
}
var quantity: Int
get() = jo.optInt("quantity", -1)
get() = jo.optInt("quantity")
set(value) {
if (value >= 0)
if (value > 0)
jo.put("quantity", value)
else
jo.remove("quantity")
}
var notes: String
get(): String = jo.optString("notes")
@@ -91,6 +94,18 @@ class LunaEvent: Comparable<LunaEvent> {
this.quantity = quantity
}
fun getStartTime(): Long {
return time
}
fun getEndTime(): Long {
return if (type == TYPE_SLEEP) {
time + quantity
} else {
time
}
}
fun getTypeEmoji(context: Context): String {
return context.getString(
when (type) {
@@ -109,6 +124,7 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_FOOD -> R.string.event_food_type
TYPE_PUKE -> R.string.event_puke_type
TYPE_BATH -> R.string.event_bath_type
TYPE_SLEEP -> R.string.event_sleep_type
else -> R.string.event_unknown_type
}
)
@@ -132,6 +148,7 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_FOOD -> R.string.event_food_desc
TYPE_PUKE -> R.string.event_puke_desc
TYPE_BATH -> R.string.event_bath_desc
TYPE_SLEEP -> R.string.event_sleep_desc
else -> R.string.event_unknown_desc
}
)
@@ -147,6 +164,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_sleep_dialog_description
else -> R.string.log_unknown_dialog_description
}
)

View File

@@ -62,10 +62,8 @@ class DateUtils {
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
} else if (hours > 0) {
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
} else if (minutes > 0) {
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)
} else {
return context.getString(R.string.now)
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)
}
}

View File

@@ -7,6 +7,7 @@ import android.os.Build
import android.util.Log
import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.entities.LunaEvent
import utils.DateUtils.Companion.formatTimeDuration
import java.text.NumberFormat
class NumericUtils (val context: Context) {
@@ -62,27 +63,31 @@ class NumericUtils (val context: Context) {
}
fun formatEventQuantity(event: LunaEvent): String {
return formatEventQuantity(event.type, event.quantity)
}
fun formatEventQuantity(type: String, quantity: Int): String {
val formatted = StringBuilder()
if (event.quantity >= 0) {
formatted.append(when (event.type) {
if (quantity > 0) {
formatted.append(when (type) {
LunaEvent.TYPE_TEMPERATURE ->
(event.quantity / 10.0f).toString()
(quantity / 10.0f).toString()
LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> {
val array = context.resources.getStringArray(R.array.AmountLabels)
return array.getOrElse(event.quantity) {
Log.e("NumericUtils", "Invalid index ${event.quantity}")
return array.getOrElse(quantity) {
Log.e("NumericUtils", "Invalid index $quantity")
return ""
}
}
else ->
event.quantity
LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, quantity.toLong())
else -> quantity
})
formatted.append(" ")
formatted.append(
when (event.type) {
when (type) {
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
@@ -90,6 +95,11 @@ class NumericUtils (val context: Context) {
else -> ""
}
)
} else {
formatted.append(when (type) {
LunaEvent.TYPE_SLEEP -> "💤" // baby is sleeping
else -> ""
})
}
return formatted.toString()
}

View File

@@ -0,0 +1,55 @@
<?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"
android:padding="10dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<com.github.mikephil.charting.charts.HorizontalBarChart
android:id="@+id/bar_chart"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<TextView
android:id="@+id/no_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
android:gravity="center"
android:text="No Data"/>
</FrameLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<Spinner
android:id="@+id/type_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1" />
<Spinner
android:id="@+id/data_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
<Spinner
android:id="@+id/time_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>

View File

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

View File

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

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/dialog_date_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="20sp"
android:text="💤"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="20dp"
android:layout_marginHorizontal="10dp"
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="-5"/>
<Button
android:id="@+id/dialog_date_duration_now"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/now"/>
<Button
android:id="@+id/dialog_date_duration_plus5"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="+5"/>
</LinearLayout>
<TextView
android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="20dp"/>
</LinearLayout>

View File

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

View File

@@ -3,14 +3,13 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<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"
android:layout_marginEnd="20dp"/>
android:layout_height="wrap_content"/>
</LinearLayout>

View File

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

View File

@@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -46,6 +45,18 @@
android:textSize="28sp"
android:text="@string/dialog_event_detail_quantity"/>
<TextView
android:id="@+id/dialog_event_detail_type_date_end"
android:layout_width="wrap_content"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:drawablePadding="10dp"
android:drawableTint="@color/accent"
android:visibility="gone"
android:textSize="16sp"
android:textStyle="bold"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="200dp"

View File

@@ -10,10 +10,21 @@
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/button_statistics"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="📊 Statistics"/>
<TextView
android:id="@+id/button_medicine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
@@ -49,6 +60,16 @@
style="@style/OverflowMenuText"
android:text="@string/overflow_event_puke"/>
<TextView
android:id="@+id/button_sleep"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_sleep"/>
<TextView
android:id="@+id/button_colic"
android:layout_width="match_parent"

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="20sp"
android:gravity="center"
android:padding="5dip" />

View File

@@ -6,4 +6,37 @@
<item>@string/amount_normal</item>
<item>@string/amount_plenty</item>
</string-array>
<string-array name="StatisticsTypeLabels">
<item>Bottle</item>
<item>Sleep</item>
</string-array>
<string-array name="StatisticsTypeValues">
<item>BOTTLE</item>
<item>SLEEP</item>
</string-array>
<string-array name="StatisticsDataLabels">
<item>Event</item>
<item>Amount</item>
</string-array>
<string-array name="StatisticsDataValues">
<item>EVENT</item>
<item>AMOUNT</item>
</string-array>
<string-array name="StatisticsTimeLabels">
<item>Day</item>
<item>Week</item>
<item>Month</item>
</string-array>
<string-array name="StatisticsTimeValues">
<item>DAY</item>
<item>WEEK</item>
<item>MONTH</item>
</string-array>
</resources>

View File

@@ -18,6 +18,7 @@
<string name="event_colic_type" translatable="false">💨</string>
<string name="event_puke_type" translatable="false">🤮</string>
<string name="event_bath_type" translatable="false">🛁</string>
<string name="event_sleep_type" translatable="false">💤</string>
<string name="event_unknown_type" translatable="false">\?</string>
<string name="event_bottle_desc">Baby bottle</string>
@@ -35,6 +36,7 @@
<string name="event_colic_desc">Gaseous colic</string>
<string name="event_puke_desc">Puke</string>
<string name="event_bath_desc">Bath</string>
<string name="event_sleep_desc">Sleep</string>
<string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Weight</string>
@@ -44,12 +46,14 @@
<string name="overflow_event_temperature">🌡️ Temperature</string>
<string name="overflow_event_colic">💨 Gaseous colic</string>
<string name="overflow_event_puke">🤮 Puke</string>
<string name="overflow_event_sleep">💤 Sleep</string>
<string name="overflow_event_bath">🛁 Bath</string>
<string name="toast_event_added">Event logged</string>
<string name="toast_logbook_saved">Logbook saved</string>
<string name="toast_event_add_error">Unable to log the event</string>
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
<string name="toast_date_error">Invalid date.</string>
<string name="now">now</string>
<string name="second_ago">sec</string>
@@ -73,6 +77,8 @@
<string name="no_connection_go_to_settings">Settings</string>
<string name="no_connection_retry">Retry</string>
<string name="statistics_title">Statistics</string>
<string name="settings_title">Settings</string>
<string name="settings_signature">Signature</string>
<string name="settings_signature_desc">Attach a signature to each event you create and for others to see. Useful if multiple people add events.</string>
@@ -112,6 +118,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_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>
@@ -127,7 +134,8 @@
<string name="row_luna_event_time">Time</string>
<string name="dialog_event_detail_title">Event detail</string>
<string name="dialog_event_detail_close_button">OK</string>
<string name="dialog_event_detail_close_button">Close</string>
<string name="dialog_event_detail_save_button">Save</string>
<string name="dialog_event_detail_delete_button">Delete</string>
<string name="dialog_event_detail_quantity">Quantity</string>
<string name="dialog_event_detail_notes">Notes</string>

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.13.0"
agp = "8.12.0"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
@@ -9,6 +9,8 @@ lifecycleRuntimeKtx = "2.6.1"
activityCompose = "1.8.0"
composeBom = "2024.04.01"
appcompat = "1.7.0"
mpandroidchart = "v4.2.2"
mpandroidchartVersion = "v3.1.0"
recyclerview = "1.3.2"
material = "1.12.0"
@@ -30,6 +32,8 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" }
mpandroidchart-vv310 = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchartVersion" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

View File

@@ -16,7 +16,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = "https://jitpack.io")
maven(url = uri("https://jitpack.io"))
}
}