7 Commits

Author SHA1 Message Date
6b1a82d7d0 add sleep event 2025-11-14 21:42:35 +01:00
2c502444d6 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-14 18:09:21 +01:00
d71e9cdf95 strings: rename scale to weight in identifiers 2025-11-14 18:07:42 +01:00
b58350543a LunaEvent: remove quantity when the value is invalid 2025-11-14 18:07:27 +01:00
1cfaf17d47 notes: add icons to use previous/next event as template 2025-11-14 18:01:24 +01:00
b271f63cb7 MainActivity: preset quantity of bottle, weight and temperature
Use the quantity of previous events
to initialize new events.
2025-11-14 09:26:16 +01:00
563e431c1d events: allow editing of all used values
1. Allow to change the date/time and
other relevant values of an event
on creation and after it was created.

2. Harmonize layout file names and
variable names.
2025-11-13 11:40:59 +01:00
20 changed files with 108 additions and 735 deletions

View File

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

View File

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

View File

@@ -6,14 +6,12 @@ import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.text.Editable
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.PopupWindow import android.widget.PopupWindow
@@ -51,8 +49,6 @@ class MainActivity : AppCompatActivity() {
const val TAG = "MainActivity" const val TAG = "MainActivity"
const val UPDATE_EVERY_SECS: Long = 30 const val UPDATE_EVERY_SECS: Long = 30
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
// list of all events
var allEvents = arrayListOf<LunaEvent>()
} }
var logbook: Logbook? = null var logbook: Logbook? = null
@@ -148,8 +144,7 @@ class MainActivity : AppCompatActivity() {
if (logbook == null) if (logbook == null)
Log.w(TAG, "showLogbook(): logbook is null!") Log.w(TAG, "showLogbook(): logbook is null!")
allEvents = logbook?.logs ?: arrayListOf() setListAdapter(logbook?.logs ?: arrayListOf())
setListAdapter(allEvents)
} }
override fun onStart() { override fun onStart() {
@@ -196,6 +191,10 @@ class MainActivity : AppCompatActivity() {
super.onStop() super.onStop()
} }
fun getAllEvents(): ArrayList<LunaEvent> {
return logbook?.logs ?: arrayListOf()
}
fun addBabyBottleEvent(event: LunaEvent) { fun addBabyBottleEvent(event: LunaEvent) {
setToPreviousQuantity(event) setToPreviousQuantity(event)
askBabyBottleContent(event, true) { askBabyBottleContent(event, true) {
@@ -331,7 +330,7 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun datePickerHelper(time: Long, dateTextView: TextView, onChange: (Long) -> Unit = {}): Calendar { fun datePickerHelper(time: Long, dateTextView: TextView, onChange: () -> Unit = {}): Calendar {
dateTextView.text = DateUtils.formatDateTime(time) dateTextView.text = DateUtils.formatDateTime(time)
val dateTime = Calendar.getInstance() val dateTime = Calendar.getInstance()
@@ -350,7 +349,7 @@ class MainActivity : AppCompatActivity() {
{ _, hour, minute -> { _, hour, minute ->
dateTime.set(year, month, day, hour, minute) dateTime.set(year, month, day, hour, minute)
dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000) dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000)
onChange.invoke(dateTime.time.time / 1000) onChange.invoke()
}, },
startHour, startHour,
startMinute, startMinute,
@@ -363,7 +362,7 @@ class MainActivity : AppCompatActivity() {
} }
fun saveEvent(event: LunaEvent) { fun saveEvent(event: LunaEvent) {
if (!allEvents.contains(event)) { if (!getAllEvents().contains(event)) {
// new event // new event
logEvent(event) logEvent(event)
} }
@@ -384,70 +383,51 @@ class MainActivity : AppCompatActivity() {
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
val fromTextView = dialogView.findViewById<TextView>(R.id.dialog_date_from)
val toTextView = dialogView.findViewById<TextView>(R.id.dialog_date_to)
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 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 var pickedFromTime = Calendar.getInstance()
val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger) var pickedToTime = Calendar.getInstance()
var duration = event.quantity fun isValidTime(fromSeconds: Long, toSeconds: Long): Boolean {
if (fromSeconds < toSeconds) {
fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean { val durationSeconds = toSeconds - fromSeconds
val now = Calendar.getInstance().time.time / 1000 // sleep between 0 seconds and 12 hours
return (timeSeconds + durationSeconds) <= now && durationSeconds < (12 * 60 * 60) return durationSeconds > 0 && durationSeconds < (12 * 60 * 60)
}
val onDateChange = { time: Long ->
durationTextView.setTextColor(currentDurationTextColor)
if (duration == 0) {
// baby is sleeping
durationTextView.text = "💤"
} else { } else {
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration.toLong()) return false
if (!isValidTime(time, duration)) {
durationTextView.setTextColor(invalidDurationTextColor)
}
} }
} }
val pickedDateTime = datePickerHelper(event.time, datePicker, onDateChange) val onDateChange = {
val fromSeconds = pickedFromTime.time.time / 1000
val toSeconds = pickedToTime.time.time / 1000
onDateChange(pickedDateTime.time.time / 1000) durationTextView.text = DateUtils.formatTimeDuration(applicationContext, toSeconds - fromSeconds)
fun adjust(minutes: Int) { if (isValidTime(fromSeconds, toSeconds)) {
duration += minutes * 60 // valid duration: set default color
if (duration < 0) { durationTextView.setTextColor(durationTextView.textColors.defaultColor)
duration = 0 } else {
} // invalid duration: set danger color
onDateChange(pickedDateTime.time.time / 1000) durationTextView.setTextColor(ContextCompat.getColor(this, R.color.danger))
}
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)
} }
} }
pickedFromTime = datePickerHelper(event.time, fromTextView, onDateChange)
pickedToTime = datePickerHelper(event.time + event.quantity, toTextView, onDateChange)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val time = pickedDateTime.time.time / 1000 val fromSeconds = pickedFromTime.time.time / 1000
val toSeconds = pickedToTime.time.time / 1000
if (isValidTime(time, duration)) { if (isValidTime(fromSeconds, toSeconds)) {
event.time = time event.time = fromSeconds
event.quantity = duration event.quantity = (toSeconds - fromSeconds).toInt()
onPositive() onPositive()
} else { } else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
} }
dialogInterface.dismiss() dialogInterface.dismiss()
} }
@@ -557,11 +537,11 @@ class MainActivity : AppCompatActivity() {
val nextTextView = dialogView.findViewById<TextView>(R.id.notes_template_next) val nextTextView = dialogView.findViewById<TextView>(R.id.notes_template_next)
val prevTextView = dialogView.findViewById<TextView>(R.id.notes_template_prev) 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) { fun updateContent(current: LunaEvent) {
val prevEvent = getPreviousSameEvent(current, templates) val allEvents = getAllEvents()
var nextEvent = getNextSameEvent(current, templates) val prevEvent = getPreviousSameEvent(current, allEvents)
var nextEvent = getNextSameEvent(current, allEvents)
notesET.setText(current.notes) notesET.setText(current.notes)
if (useQuantity) { if (useQuantity) {
@@ -612,7 +592,7 @@ class MainActivity : AppCompatActivity() {
updateContent(event) updateContent(event)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val notes = notesET.text.toString().trim() val notes = notesET.text.toString()
if (useQuantity) { if (useQuantity) {
val quantity = qtyET.text.toString().toIntOrNull() val quantity = qtyET.text.toString().toIntOrNull()
@@ -622,7 +602,7 @@ class MainActivity : AppCompatActivity() {
event.quantity = quantity event.quantity = quantity
onPositive() onPositive()
} else { } else {
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
} }
} else { } else {
@@ -663,13 +643,13 @@ class MainActivity : AppCompatActivity() {
} }
fun setToPreviousQuantity(event: LunaEvent) { fun setToPreviousQuantity(event: LunaEvent) {
val prev = getPreviousSameEvent(event, allEvents) val prev = getPreviousSameEvent(event, getAllEvents())
if (prev != null) { if (prev != null) {
event.quantity = prev.quantity event.quantity = prev.quantity
} }
} }
fun getPreviousSameEvent(event: LunaEvent, items: List<LunaEvent>): LunaEvent? { fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
var previousEvent: LunaEvent? = null var previousEvent: LunaEvent? = null
for (item in items) { for (item in items) {
if (item.type == event.type && item.time < event.time) { if (item.type == event.type && item.time < event.time) {
@@ -683,7 +663,7 @@ class MainActivity : AppCompatActivity() {
return previousEvent return previousEvent
} }
fun getNextSameEvent(event: LunaEvent, items: List<LunaEvent>): LunaEvent? { fun getNextSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
var nextEvent: LunaEvent? = null var nextEvent: LunaEvent? = null
for (item in items) { for (item in items) {
if (item.type == event.type && item.time > event.time) { if (item.type == event.type && item.time > event.time) {
@@ -700,12 +680,6 @@ class MainActivity : AppCompatActivity() {
fun showEventDetailDialog(originalEvent: LunaEvent) { fun showEventDetailDialog(originalEvent: LunaEvent) {
val event = LunaEvent(originalEvent) 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 // Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
pauseLogbookUpdate = true pauseLogbookUpdate = true
@@ -716,70 +690,19 @@ class MainActivity : AppCompatActivity() {
val emojiTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji) 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 descriptionTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description)
val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date) val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
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 quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
val notesTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes) val notesTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes)
emojiTextView.text = event.getTypeEmoji(this) emojiTextView.text = event.getTypeEmoji(this)
descriptionTextView.text = event.getTypeDescription(this) descriptionTextView.text = event.getTypeDescription(this)
d.setView(dialogView) val pickedTime = datePickerHelper(event.time, dateTextView)
d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i ->
deleteEvent(originalEvent)
dialogInterface.dismiss()
}
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
saveEvent(originalEvent)
}
dialogInterface.dismiss()
}
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i ->
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(
ContextCompat.getColor(this, R.color.danger)
)
alertDialog.setOnDismissListener {
// Resume logbook update
pauseLogbookUpdate = false
}
val updateValues = { val updateValues = {
quantityTextView.text = NumericUtils(this).formatEventQuantity(event) quantityTextView.text = NumericUtils(this).formatEventQuantity(event)
notesTextView.text = event.notes 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() updateValues()
datePickerHelper(event.time, dateTextView, { newTime: Long ->
event.time = newTime
updateValues()
})
quantityTextView.setOnClickListener { quantityTextView.setOnClickListener {
when (event.type) { when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues) LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
@@ -801,6 +724,40 @@ class MainActivity : AppCompatActivity() {
} }
} }
d.setView(dialogView)
d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i ->
deleteEvent(originalEvent)
dialogInterface.dismiss()
}
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i ->
event.time = pickedTime.time.time / 1000
if (event.time != originalEvent.time
|| event.quantity != originalEvent.quantity
|| event.notes != originalEvent.notes) {
originalEvent.time = event.time
originalEvent.quantity = event.quantity
originalEvent.notes = event.notes
saveEvent(originalEvent)
}
dialogInterface.dismiss()
}
val alertDialog = d.create()
alertDialog.show()
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(
ContextCompat.getColor(this, R.color.danger)
)
alertDialog.setOnDismissListener {
// Resume logbook update
pauseLogbookUpdate = false
}
// show optional signature // show optional signature
if (event.signature.isNotEmpty()) { if (event.signature.isNotEmpty()) {
val signatureTextEdit = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature) val signatureTextEdit = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature)
@@ -808,6 +765,8 @@ class MainActivity : AppCompatActivity() {
signatureTextEdit.visibility = View.VISIBLE signatureTextEdit.visibility = View.VISIBLE
} }
val allEvents = getAllEvents()
// create link to prevent event of the same type // create link to prevent event of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous) val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
val previousEvent = getPreviousSameEvent(event, allEvents) val previousEvent = getPreviousSameEvent(event, allEvents)
@@ -940,7 +899,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
loadLogbookList() loadLogbookList()
Toast.makeText(this@MainActivity, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, getString(R.string.logbook_created) + logbookName, Toast.LENGTH_SHORT).show()
}) })
} }
@@ -1097,7 +1056,7 @@ class MainActivity : AppCompatActivity() {
setLoading(false) setLoading(false)
Toast.makeText( Toast.makeText(
this@MainActivity, applicationContext,
if (lastEventAdded != null) if (lastEventAdded != null)
R.string.toast_event_added R.string.toast_event_added
else else
@@ -1159,7 +1118,7 @@ class MainActivity : AppCompatActivity() {
runOnUiThread({ runOnUiThread({
setLoading(false) setLoading(false)
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show() Toast.makeText(applicationContext, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
savingEvent(false) savingEvent(false)
}) })
@@ -1227,16 +1186,6 @@ class MainActivity : AppCompatActivity() {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BATH)) addPlainEvent(LunaEvent(LunaEvent.TYPE_BATH))
dismiss() 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 -> }.also { popupWindow ->
popupWindow.setOnDismissListener({ popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({ Handler(mainLooper).postDelayed({

View File

@@ -1,375 +0,0 @@
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 LunaEvent.TYPE_CUSTOM -> item.notes
else -> item.getTypeDescription(context) else -> item.getTypeDescription(context)
} }
holder.time.text = DateUtils.formatTimeAgo(context, item.getEndTime()) holder.time.text = DateUtils.formatTimeAgo(context, item.time)
var quantityText = numericUtils.formatEventQuantity(item) var quantityText = numericUtils.formatEventQuantity(item)
// if the event is weight, show difference with the last one // if the event is weight, show difference with the last one

View File

@@ -94,18 +94,6 @@ class LunaEvent: Comparable<LunaEvent> {
this.quantity = quantity 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 { fun getTypeEmoji(context: Context): String {
return context.getString( return context.getString(
when (type) { when (type) {

View File

@@ -62,8 +62,10 @@ class DateUtils {
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago) return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
} else if (hours > 0) { } else if (hours > 0) {
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago) return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
} else { } else if (minutes > 0) {
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago) 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)
} }
} }

View File

@@ -63,31 +63,28 @@ class NumericUtils (val context: Context) {
} }
fun formatEventQuantity(event: LunaEvent): String { fun formatEventQuantity(event: LunaEvent): String {
return formatEventQuantity(event.type, event.quantity)
}
fun formatEventQuantity(type: String, quantity: Int): String {
val formatted = StringBuilder() val formatted = StringBuilder()
if (quantity > 0) { if (event.quantity > 0) {
formatted.append(when (type) { formatted.append(when (event.type) {
LunaEvent.TYPE_TEMPERATURE -> LunaEvent.TYPE_TEMPERATURE ->
(quantity / 10.0f).toString() (event.quantity / 10.0f).toString()
LunaEvent.TYPE_DIAPERCHANGE_POO, LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE, LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> { LunaEvent.TYPE_PUKE -> {
val array = context.resources.getStringArray(R.array.AmountLabels) val array = context.resources.getStringArray(R.array.AmountLabels)
return array.getOrElse(quantity) { return array.getOrElse(event.quantity) {
Log.e("NumericUtils", "Invalid index $quantity") Log.e("NumericUtils", "Invalid index ${event.quantity}")
return "" return ""
} }
} }
LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, quantity.toLong()) LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, event.quantity.toLong())
else -> quantity else ->
event.quantity
}) })
formatted.append(" ") formatted.append(" ")
formatted.append( formatted.append(
when (type) { when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
@@ -95,11 +92,6 @@ class NumericUtils (val context: Context) {
else -> "" else -> ""
} }
) )
} else {
formatted.append(when (type) {
LunaEvent.TYPE_SLEEP -> "💤" // baby is sleeping
else -> ""
})
} }
return formatted.toString() return formatted.toString()
} }

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
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

@@ -11,7 +11,8 @@
android:id="@+id/dialog_amount_value" android:id="@+id/dialog_amount_value"
android:layout_width="250dp" android:layout_width="250dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingHorizontal="16dp"/> android:paddingHorizontal="16dp"
android:paddingVertical="8dp"/>
<TextView <TextView
android:id="@+id/dialog_date_picker" android:id="@+id/dialog_date_picker"

View File

@@ -1,55 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
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

@@ -52,6 +52,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center" android:layout_gravity="center"
android:layout_marginTop="15dp"/> android:layout_marginTop="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -10,6 +10,7 @@
<TextView <TextView
android:id="@+id/dialog_date_picker" android:id="@+id/dialog_date_picker"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"/> android:layout_height="wrap_content"
android:layout_marginTop="20dp"/>
</LinearLayout> </LinearLayout>

View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@@ -45,18 +46,6 @@
android:textSize="28sp" android:textSize="28sp"
android:text="@string/dialog_event_detail_quantity"/> 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 <ScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="200dp" android:layout_height="200dp"

View File

@@ -10,21 +10,10 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> 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 <TextView
android:id="@+id/button_medicine" android:id="@+id/button_medicine"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp" android:padding="10dp"
android:background="@drawable/dropdown_list_item_background" android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText" style="@style/OverflowMenuText"

View File

@@ -1,8 +0,0 @@
<?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,37 +6,4 @@
<item>@string/amount_normal</item> <item>@string/amount_normal</item>
<item>@string/amount_plenty</item> <item>@string/amount_plenty</item>
</string-array> </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> </resources>

View File

@@ -77,8 +77,6 @@
<string name="no_connection_go_to_settings">Settings</string> <string name="no_connection_go_to_settings">Settings</string>
<string name="no_connection_retry">Retry</string> <string name="no_connection_retry">Retry</string>
<string name="statistics_title">Statistics</string>
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
<string name="settings_signature">Signature</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> <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>
@@ -118,7 +116,7 @@
<string name="log_temperature_dialog_description">Select the temperature:</string> <string name="log_temperature_dialog_description">Select the temperature:</string>
<string name="log_unknown_dialog_description"></string> <string name="log_unknown_dialog_description"></string>
<string name="log_weight_dialog_description">Insert the weight:</string> <string name="log_weight_dialog_description">Insert the weight:</string>
<string name="log_sleep_dialog_description">Set sleep duration:</string> <string name="log_sleep_dialog_description">Select sleep range:</string>
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</string> <string name="measurement_unit_liquid_base_metric" translatable="false">ml</string>
<string name="measurement_unit_weight_base_metric" translatable="false">g</string> <string name="measurement_unit_weight_base_metric" translatable="false">g</string>
@@ -134,8 +132,7 @@
<string name="row_luna_event_time">Time</string> <string name="row_luna_event_time">Time</string>
<string name="dialog_event_detail_title">Event detail</string> <string name="dialog_event_detail_title">Event detail</string>
<string name="dialog_event_detail_close_button">Close</string> <string name="dialog_event_detail_close_button">OK</string>
<string name="dialog_event_detail_save_button">Save</string>
<string name="dialog_event_detail_delete_button">Delete</string> <string name="dialog_event_detail_delete_button">Delete</string>
<string name="dialog_event_detail_quantity">Quantity</string> <string name="dialog_event_detail_quantity">Quantity</string>
<string name="dialog_event_detail_notes">Notes</string> <string name="dialog_event_detail_notes">Notes</string>

View File

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

View File

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