19 Commits

Author SHA1 Message Date
a8305b8fc9 statistics: complete removal 2026-02-12 23:13:58 +01:00
43f0519487 LunaEvent: rework sleep event
Remove adjustment buttons and
simplify design.
2026-02-12 23:01:01 +01:00
1bcfc18a71 MainActivity: rename datepicker 2026-02-12 23:01:01 +01:00
e345e98668 layout: replace menu icon with utf8 character
The three dot menu icosn looks odd when stretched
due to the dynamic menu feature. Thus replace it
with the hamburger menu character that looks better
when scaled.
2026-02-12 23:01:01 +01:00
09f5a4e27b MainActivity: sort events before saving
Also replace notifyItemInserted since it does not
call the adapter to redraw the row striping when
a new event is added.
2026-02-12 23:01:01 +01:00
b3b432525e StatisticsActivity: rework all statistics
Improve the overall code.
2026-02-12 23:00:58 +01:00
d832aa4330 NumericUtils: remove possible trailing whitespace 2026-02-09 08:23:38 +01:00
672ae37049 MainActivity: do not switch logbook on reload 2026-02-09 08:23:38 +01:00
672e58c028 LunaEvent: reorganize event text getters
Use method names that better reflect
the use of the returned text.
2026-02-09 08:23:38 +01:00
d2729af30f 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-02-09 08:23:21 +01:00
85567cce77 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-23 06:46:11 +01:00
80a51ea8ef MainActivity: increase bottle volume to 340ml
This is the maximum amount found in sold bottles.
2026-01-23 06:46:11 +01:00
28679a4a66 gradle: use uniform implementation directive for sardine-android 2026-01-23 06:46:11 +01:00
f73d3562a9 gradle: avoid inclusion of apk signing blobs
See https://android.izzysoft.de/articles/named/iod-scan-apkchecks?lang=en#blobs
2026-01-23 06:46:11 +01:00
8a2932b1e7 gradle: set compileSDK/targetSdk to 36 2026-01-23 06:46:11 +01:00
2af8989777 StatisticsActivity: add statistics for bottle and sleep events 2026-01-23 06:46:11 +01:00
b417fe48a6 MainActivity: show save button if any values has changed 2026-01-23 06:46:11 +01:00
a887d9f29f MainActivity: use unique templates for notes 2026-01-23 06:46:11 +01:00
44748506ff LunaEvent: add sleep event 2026-01-23 06:46:07 +01:00
19 changed files with 122 additions and 936 deletions

View File

@@ -65,5 +65,4 @@ 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,10 +30,6 @@
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

@@ -155,7 +155,7 @@ class MainActivity : AppCompatActivity() {
}
}
// sort all event types by frequency or ordinal
// sort all event types by frequency and ordinal
val eventTypesSorted = LunaEvent.Type.entries.toList().sortedWith(
compareBy({ -1 * (eventTypeStats[it] ?: 0) }, { it.ordinal })
).filter { it != LunaEvent.Type.UNKNOWN }
@@ -276,7 +276,7 @@ class MainActivity : AppCompatActivity() {
numberPicker.value = event.quantity / 10
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
@@ -314,7 +314,7 @@ class MainActivity : AppCompatActivity() {
weightET.setText(event.quantity.toString())
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
@@ -365,7 +365,7 @@ class MainActivity : AppCompatActivity() {
}
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
@@ -389,7 +389,8 @@ class MainActivity : AppCompatActivity() {
alertDialog.show()
}
fun datePickerHelper(time: Long, dateTextView: TextView, onChange: (Long) -> Unit = {}): Calendar {
// Pick a date/time.
fun dateTimePicker(time: Long, dateTextView: TextView, onChange: (Long) -> Unit = {}): Calendar {
dateTextView.text = DateUtils.formatDateTime(time)
val dateTime = Calendar.getInstance()
@@ -420,89 +421,83 @@ class MainActivity : AppCompatActivity() {
return dateTime
}
fun saveEvent(event: LunaEvent) {
if (!allEvents.contains(event)) {
// new event
logEvent(event)
}
logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged()
saveLogbook()
}
fun addSleepEvent(event: LunaEvent) {
askSleepValue(event) { saveEvent(event) }
askSleepValue(event, true) { saveEvent(event) }
}
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) {
fun askSleepValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getDialogTitle(this))
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 datePickerBegin = dialogView.findViewById<TextView>(R.id.dialog_date_picker_begin)
val datePickerEnd = dialogView.findViewById<TextView>(R.id.dialog_date_picker_end)
val currentDurationTextColor = durationTextView.currentTextColor
val invalidDurationTextColor = ContextCompat.getColor(this, R.color.danger)
var duration = event.quantity
// in seconds
var sleepStart = event.time
var sleepEnd = event.time + event.quantity
fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean {
fun isValidTimeSpan(timeBeginUnix: Long, timeEndUnix: Long): Boolean {
val now = System.currentTimeMillis() / 1000
return (timeSeconds + durationSeconds) <= now && durationSeconds < (12 * 60 * 60)
return (timeBeginUnix > 0)
&& (timeEndUnix > 0)
&& (timeBeginUnix <= timeEndUnix)
&& (timeBeginUnix <= now)
&& (timeEndUnix <= now)
&& (timeEndUnix - timeBeginUnix) < (24 * 60 * 60)
}
val onDateChange = { time: Long ->
durationTextView.setTextColor(currentDurationTextColor)
// prevent printing of seconds
fun adjustToMinute(unixTime: Long): Long {
return unixTime - (unixTime % 60)
}
if (duration == 0) {
fun updateDuration() {
durationTextView.setTextColor(currentDurationTextColor)
val duration = sleepEnd - sleepStart
if (duration == 0L) {
// baby is sleeping
durationTextView.text = "💤"
} else {
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration.toLong())
if (!isValidTime(time, duration)) {
durationTextView.text = DateUtils.formatTimeDuration(applicationContext, duration)
if (!isValidTimeSpan(sleepStart, sleepEnd)) {
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)
val pickedDateTimeBegin = dateTimePicker(event.time, datePickerBegin) { time: Long ->
sleepStart = adjustToMinute(time)
updateDuration()
}
durationMinus5Button.setOnClickListener { adjust(-5) }
durationPlus5Button.setOnClickListener { adjust(5) }
val pickedDateTimeEnd = dateTimePicker(event.time + event.quantity, datePickerEnd) { time: Long ->
sleepEnd = adjustToMinute(time)
updateDuration()
}
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)
}
sleepStart = adjustToMinute(pickedDateTimeBegin.time.time / 1000)
sleepEnd = adjustToMinute(pickedDateTimeEnd.time.time / 1000)
updateDuration()
if (showTime) {
datePickerEnd.visibility = View.GONE
durationTextView.visibility = View.GONE
//d.setMessage("")
} else {
durationTextView.visibility = View.VISIBLE
d.setMessage(event.getDialogMessage(this))
}
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val time = pickedDateTime.time.time / 1000
if (isValidTime(time, duration)) {
event.time = time
event.quantity = duration
if (isValidTimeSpan(sleepStart, sleepEnd)) {
event.time = sleepStart
event.quantity = (sleepEnd - sleepStart).toInt()
onPositive()
} else {
Toast.makeText(this, R.string.toast_date_error, Toast.LENGTH_SHORT).show()
@@ -519,6 +514,7 @@ class MainActivity : AppCompatActivity() {
}
fun addAmountEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askAmountValue(event, true) { saveEvent(event) }
}
@@ -535,11 +531,11 @@ class MainActivity : AppCompatActivity() {
R.array.AmountLabels,
android.R.layout.simple_spinner_dropdown_item
)
// set pre-selected item and ensure the quantity to index is in bounds
spinner.setSelection(event.quantity.coerceIn(0, spinner.count - 1))
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
@@ -572,7 +568,7 @@ class MainActivity : AppCompatActivity() {
d.setView(dialogView)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedDateTime = datePickerHelper(event.time, dateTV)
val pickedDateTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
}
@@ -607,7 +603,7 @@ class MainActivity : AppCompatActivity() {
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
val dateTV = dialogView.findViewById<TextView>(R.id.dialog_date_picker)
val pickedTime = datePickerHelper(event.time, dateTV)
val pickedTime = dateTimePicker(event.time, dateTV)
if (!showTime) {
dateTV.visibility = View.GONE
@@ -819,7 +815,7 @@ class MainActivity : AppCompatActivity() {
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.text = DateUtils.formatDateTime(event.getEndTime())
dateEndTextView.visibility = View.VISIBLE
} else {
dateEndTextView.visibility = View.GONE
@@ -833,7 +829,7 @@ class MainActivity : AppCompatActivity() {
}
updateValues()
datePickerHelper(event.time, dateTextView, { newTime: Long ->
dateTimePicker(event.time, dateTextView, { newTime: Long ->
event.time = newTime
updateValues()
})
@@ -847,7 +843,7 @@ class MainActivity : AppCompatActivity() {
LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.Type.TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.SLEEP -> askSleepValue(event, updateValues)
LunaEvent.Type.SLEEP -> askSleepValue(event, false, updateValues)
else -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
@@ -877,7 +873,7 @@ class MainActivity : AppCompatActivity() {
val previousEvent = getPreviousSameEvent(event, allEvents)
if (previousEvent != null) {
val emoji = previousEvent.getHeaderEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
val time = DateUtils.formatTimeDuration(applicationContext, event.getStartTime() - previousEvent.getEndTime())
previousTextView.text = String.format("⬅️ %s %s", emoji, time)
previousTextView.setOnClickListener {
alertDialog.cancel()
@@ -892,7 +888,7 @@ class MainActivity : AppCompatActivity() {
val nextEvent = getNextSameEvent(event, allEvents)
if (nextEvent != null) {
val emoji = nextEvent.getHeaderEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.getStartTime() - event.getEndTime())
nextTextView.text = String.format("%s %s ➡️", time, emoji)
nextTextView.setOnClickListener {
alertDialog.cancel()
@@ -1117,23 +1113,6 @@ class MainActivity : AppCompatActivity() {
})
}
fun logEvent(event: LunaEvent) {
savingEvent(true)
event.signature = signature
setLoading(true)
logbook?.logs?.add(0, event)
recyclerView.adapter?.notifyItemInserted(0)
recyclerView.smoothScrollToPosition(0)
saveLogbook(event)
// Check logbook size to avoid OOM errors
if (logbook?.isTooBig() == true) {
askToTrimLogbook()
}
}
fun deleteEvent(event: LunaEvent) {
// Update view
savingEvent(true)
@@ -1145,6 +1124,32 @@ class MainActivity : AppCompatActivity() {
saveLogbook()
}
fun saveEvent(event: LunaEvent) {
if (allEvents.contains(event)) {
// event was modified
logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged()
saveLogbook()
} else {
// add new event
savingEvent(true)
setLoading(true)
if (signature.isNotEmpty()) {
event.signature = signature
}
logbook?.logs?.add(0, event)
logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged()
recyclerView.smoothScrollToPosition(0)
saveLogbook(event)
// Check logbook size to avoid OOM errors
if (logbook?.isTooBig() == true) {
askToTrimLogbook()
}
}
}
/**
* Saves the logbook. If saving while adding an event, please specify the event so in case
* of error can be removed from the list.
@@ -1279,18 +1284,6 @@ class MainActivity : AppCompatActivity() {
val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.more_events_popup, null)
// Add statistics (hard coded)
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()
}
val linearLayout = contentView.findViewById<LinearLayout>(R.id.layout_list)
// Add buttons to create other events

View File

@@ -1,646 +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.NumericUtils
import java.util.Calendar
import java.util.Date
import kotlin.math.max
import kotlin.math.min
import androidx.core.graphics.toColorInt
class StatisticsActivity : AppCompatActivity() {
lateinit var barChart: BarChart
lateinit var noDataTextView: TextView
lateinit var graphTypeSpinner: Spinner
//lateinit var dataTypeSelection: Spinner
lateinit var timeRangeSpinner: Spinner
enum class GraphType {
BOTTLE_EVENTS,
BOTTLE_SUM,
BOTTLE_SUM_AVERAGE,
SLEEP_SUM,
SLEEP_SUM_AVERAGE,
SLEEP_EVENTS,
SLEEP_PATTERN
}
enum class RangeType {
DAY,
WEEK,
MONTH
}
// default selection
var graphTypeSelection = GraphType.SLEEP_SUM
var timeRangeSelection = RangeType.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)
graphTypeSpinner = findViewById(R.id.type_selection)
//dataTypeSelection = findViewById(R.id.data_selection)
timeRangeSpinner = findViewById(R.id.time_selection)
setupSpinner("SLEEP_SUM",
R.id.type_selection,
R.array.StatisticsTypeLabels,
R.array.StatisticsTypeValues,
object : SpinnerItemSelected {
override fun call(newValue: String?) {
newValue ?: return
graphTypeSelection = when (newValue) {
"BOTTLE_EVENTS" -> GraphType.BOTTLE_EVENTS
"BOTTLE_SUM" -> GraphType.BOTTLE_SUM
"BOTTLE_SUM_AVERAGE" -> GraphType.BOTTLE_SUM_AVERAGE
"SLEEP_SUM_AVERAGE" -> GraphType.SLEEP_SUM_AVERAGE
"SLEEP_SUM" -> GraphType.SLEEP_SUM
"SLEEP_SUM_AVERAGE" -> GraphType.SLEEP_SUM_AVERAGE
"SLEEP_EVENTS" -> GraphType.SLEEP_EVENTS
"SLEEP_PATTERN" -> GraphType.SLEEP_PATTERN
else -> {
Log.e(TAG, "Invalid graph type selection: $newValue")
return
}
}
//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("DAY",
R.id.time_selection,
R.array.StatisticsTimeLabels,
R.array.StatisticsTimeValues,
object : SpinnerItemSelected {
override fun call(newValue: String?) {
newValue ?: return
timeRangeSelection = when (newValue) {
"DAY" -> RangeType.DAY
"WEEK" -> RangeType.WEEK
"MONTH" -> RangeType.MONTH
else -> {
Log.e(TAG, "Invalid time range selection: $newValue")
return
}
}
//Log.d("event", "new value: $newValue")
updateGraph()
}
}
)
updateGraph()
}
fun showSleepBarGraph(events: List<LunaEvent>, unixToSpan: (Long) -> Int, spanToUnix: (Int) -> Long) {
fun getEndTime(event: LunaEvent): Long {
if (event.quantity == 0) {
// sleep is still ongoing
val dateTime = Calendar.getInstance()
return (dateTime.time.time / 1000) - event.time
} else {
return event.quantity.toLong()
}
}
data class SleepRange(val start: Long, val end: Long)
val ranges = arrayListOf<SleepRange>()
// Transform events into time ranges.
// Merge overlapping times and extend
// ongoing sleep events until now.
val dateTime = Calendar.getInstance()
for (event in events) {
val endTime = if (event.quantity == 0) {
dateTime.time.time / 1000 // now
} else {
event.time + event.quantity
}
/*
// TODO: handle overlap
if (ranges.isNotEmpty()) {
val lastItem = ranges.lastItem()
if (lastItem.start)
if (lastItem.end <= event.time) {
// distinct
}
}
}
*/
ranges.add(SleepRange(event.time, endTime))
}
// unix time span of all events
val startUnix = events.minOf { it.time }
val endUnix = events.maxOf { getEndTime(it) }
// convert to days, weeks or months
val startSpan = unixToSpan(startUnix)
val endSpan = unixToSpan(endUnix)
val values = ArrayList<BarEntry>()
fun countEvent(index: Int) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), 0F))
}
// update value
values[index].y += 1F
}
fun accumulateValue(index: Int, value: Long) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), 0F))
}
// update value
values[index].y += value.toFloat()
}
fun stackValue(index: Int, value: Long) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), FloatArray(0)))
}
val x = values[index].x
val yVals = values[index].yVals
// update value
val newYVals = appendToFloatArray(yVals, value.toFloat())
values[index] = BarEntry(x, newYVals)
}
// awake/sleep
fun stackValuePattern(index: Int, spanBegin: Long, spanEnd: Long, begin: Long, end: Long) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), FloatArray(0)))
}
assert(begin in spanBegin..spanEnd)
assert(end in spanBegin..spanEnd)
assert(begin <= end)
val x = values[index].x
val yVals = values[index].yVals // alternating sleep/awake durations
val y = yVals.fold(0F) { acc, next -> acc + next }
// y value is seconds when last awake
val awakeDuration = max(begin - spanBegin - y.toLong(), 0L)
val sleepDuration = end - begin
if ((awakeDuration + sleepDuration) > (spanEnd - spanBegin)) {
Log.e(TAG, "Invalid sleep duration, exceeds day/week or month bounds => ignore value")
return
}
// update value
val newYVals = appendToFloatArray(yVals, awakeDuration.toFloat(), sleepDuration.toFloat())
values[index] = BarEntry(x, newYVals)
}
/*
fun addStack24hCap(spanDuration: Long) {
// spanDuration is usually a day, week, month in seconds
Log.d(TAG, "spanDuration: $spanDuration, ${24*60*60}")
for (i in values.indices) {
val x = values[i].x
val yVals = values[i].yVals
val y = yVals.fold(0F) { acc, next -> acc + next }
val cap = spanDuration.toFloat() - y
if (cap >= 0F) {
// Add a cap value and an 0 value to keep the number of spans even.
// This is important, since we configure two colors and they will alternate.
val newYVals = appendToFloatArray(yVals, cap, 0F)
values[i] = BarEntry(x, newYVals)
} else {
Log.e(TAG, "Invalid remaining sleep duration, exceeds day/week or month bounds => ignore")
}
}
}
*/
for (range in ranges) {
// a sleep event can span to another day
// distribute sleep time over the days
val startUnix = range.start //event.time
val endUnix = range.end //getEndTime(event)
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 sleepBegin = max(mid, spanBegin)
val sleepEnd = min(endUnix, spanEnd)
val index = i - startSpan
val duration = sleepEnd - sleepBegin
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${formatTimeDuration(this, duration)}")
if (graphTypeSelection == GraphType.SLEEP_PATTERN) {
stackValuePattern(index, spanBegin, spanEnd, sleepBegin, sleepEnd)
} else if (graphTypeSelection == GraphType.SLEEP_SUM) {
stackValue(index, duration)
} else if (graphTypeSelection == GraphType.SLEEP_SUM_AVERAGE) {
accumulateValue(index, duration)
} else if (graphTypeSelection == GraphType.SLEEP_EVENTS) {
countEvent(index)
} else {
Log.e(TAG, "Unexpected graph type.")
return
}
mid = sleepEnd
}
// TODO: move addStack24h here, since the spans can have different length (edge case and does not really matter)
}
//addStack24hCap(spanToUnix(1) - spanToUnix(0))
// for debugging
for (value in values) {
val y = value.yVals.fold(0F) { acc, next -> acc + next }
val yVals = value.yVals.joinToString { it.toString() }
Log.d(TAG, "value: ${value.x} $y ($yVals)")
}
// list of dates
val labels = ArrayList<String>()
for (index in values.indices) {
labels.add(spanToLabel(spanToUnix(startSpan + index)))
}
val set1 = BarDataSet(values, "")
if (graphTypeSelection == GraphType.SLEEP_PATTERN) {
// awake phase color is transparent
set1.colors = arrayListOf("#00000000".toColorInt(), ColorTemplate.rgb("#72d7f5"))
set1.setDrawValues(false) // too many values => let's disable it
} else {
set1.setDrawValues(false)
}
set1.setDrawIcons(false)
barChart.legend.isEnabled = false
barChart.xAxis.setLabelCount(min(values.size, 24))
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 NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_SLEEP, value.toInt())
}
})
barChart.xAxis.valueFormatter = object: ValueFormatter() {
override fun getFormattedValue(value: Float): String {
return labels.getOrElse(value.toInt(), {"?"})
}
}
//data.setValueTextSize(12f)
barChart.setData(data)
barChart.invalidate()
}
fun showBottleBarGraph(events: List<LunaEvent>, unixToSpan: (Long) -> Int, spanToUnix: (Int) -> Long) {
// unix time span of all events
val startUnix = events.minOf { it.time }
val endUnix = events.maxOf { it.time }
// convert to days, weeks or months
val startSpan = unixToSpan(startUnix)
val endSpan = unixToSpan(endUnix)
val values = ArrayList<BarEntry>()
fun countValue(index: Int) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), 0F))
}
// update value
values[index].y += 1F
}
fun accumulateValue(index: Int, duration: Long) {
// create initial values
while (index >= values.size) {
values.add(BarEntry(values.size.toFloat(), 0F))
}
// update value
values[index].y += duration.toFloat()
}
for (event in events) {
if (graphTypeSelection == GraphType.BOTTLE_EVENTS) {
val index = unixToSpan(event.time) - startSpan
countValue(index)
} else if (graphTypeSelection == GraphType.BOTTLE_SUM) {
val index = unixToSpan(event.time) - startSpan
//Log.d(TAG, "[${index}] ${event.quantity}")
accumulateValue(index, event.quantity.toLong())
} else {
Log.e(TAG, "unhandled graphTypeSelection")
return
}
}
// list of dates
val labels = ArrayList<String>()
for (index in values.indices) {
labels.add(spanToLabel(spanToUnix(startSpan + index)))
}
val set1 = BarDataSet(values, "")
set1.setDrawValues(true)
set1.setDrawIcons(false)
//showGraph(set1, valueLabels)
// for debugging
//val sum1 = allEvents.fold(0) { acc, event -> acc + event.quantity }
//val sum2 = values.fold(0F) { acc, item -> acc + item.y }
//Log.d(TAG, "sum1: $sum1, sum2: $sum2")
barChart.xAxis.setLabelCount(min(values.size, 24))
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 (graphTypeSelection) {
GraphType.BOTTLE_EVENTS -> value.toInt().toString()
GraphType.BOTTLE_SUM -> NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_BABY_BOTTLE, value.toInt())
else -> {
Log.e(TAG, "unhandled graphTypeSelection")
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()
}
fun spanToLabel(unixSeconds: Long): String {
val dateTime = Calendar.getInstance()
dateTime.time = Date(1000L * unixSeconds)
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 (timeRangeSelection) {
RangeType.DAY -> "$day/$month/$year"
RangeType.WEEK -> "$week/$year"
RangeType.MONTH -> "$month/$year"
}
}
fun updateGraph() {
val unixToSpan = when (timeRangeSelection) {
RangeType.DAY -> { unix: Long -> unixToDays(unix) }
RangeType.WEEK -> { unix: Long -> unixToWeeks(unix) }
RangeType.MONTH -> { unix: Long -> unixToMonths(unix) }
}
val spanToUnix = when (timeRangeSelection) {
RangeType.DAY -> { span: Int -> daysToUnix(span) }
RangeType.WEEK -> { span: Int -> weeksToUnix(span) }
RangeType.MONTH -> { span: Int -> monthsToUnix(span) }
}
val eventType = when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS,
GraphType.BOTTLE_SUM,
GraphType.BOTTLE_SUM_AVERAGE -> LunaEvent.TYPE_BABY_BOTTLE
GraphType.SLEEP_SUM,
GraphType.SLEEP_SUM_AVERAGE,
GraphType.SLEEP_EVENTS,
GraphType.SLEEP_PATTERN -> LunaEvent.TYPE_SLEEP
}
val events = MainActivity.allEvents.filter { it.type == eventType }.sortedBy { it.time }
if (events.isEmpty()) {
barChart.visibility = View.GONE
noDataTextView.visibility = View.VISIBLE
} else {
barChart.visibility = View.VISIBLE
noDataTextView.visibility = View.GONE
when (graphTypeSelection) {
GraphType.BOTTLE_EVENTS,
GraphType.BOTTLE_SUM,
GraphType.BOTTLE_SUM_AVERAGE -> showBottleBarGraph(events, unixToSpan, spanToUnix)
GraphType.SLEEP_SUM,
GraphType.SLEEP_SUM_AVERAGE,
GraphType.SLEEP_EVENTS,
GraphType.SLEEP_PATTERN -> showSleepBarGraph(events, unixToSpan, spanToUnix)
}
}
}
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)
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)
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
}
fun appendToFloatArray(array: FloatArray, vararg values: Float): FloatArray {
// create new array
val newArray = FloatArray(array.size + values.size)
// copy old values
for (i in array.indices) {
newArray[i] = array[i]
}
// add new values
for (i in values.indices) {
newArray[array.size + i] = values[i]
}
return newArray
}
}
}

View File

@@ -58,12 +58,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
LunaEvent.Type.NOTE -> item.notes
else -> item.getRowItemTitle(context)
}
val endTime = if (item.type == LunaEvent.Type.SLEEP) {
item.quantity + item.time
} else {
item.time
}
holder.time.text = DateUtils.formatTimeAgo(context, endTime)
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

@@ -104,7 +104,7 @@ class LunaEvent: Comparable<LunaEvent> {
}
fun getDialogTitle(context: Context): String {
return getTypeDescription(context, type)
return getDialogTitle(context, type)
}
fun getRowItemTitle(context: Context): String {
@@ -115,6 +115,18 @@ class LunaEvent: Comparable<LunaEvent> {
return getDialogMessage(context, type)
}
fun getStartTime(): Long {
return time
}
fun getEndTime(): Long {
return if (type == Type.SLEEP) {
time + quantity
} else {
time
}
}
fun toJson(): JSONObject {
return jo
}
@@ -168,7 +180,7 @@ class LunaEvent: Comparable<LunaEvent> {
)
}
fun getTypeDescription(context: Context, type: Type): String {
fun getDialogTitle(context: Context, type: Type): String {
return context.getString(
when (type) {
Type.BABY_BOTTLE -> R.string.event_bottle_desc

View File

@@ -56,11 +56,11 @@ class DateUtils {
return builder.toString()
}
if (years > 0) {
if (years != 0L) {
return format(years, days, R.string.year_ago, R.string.years_ago, R.string.day_ago, R.string.days_ago)
} else if (days > 0) {
} else if (days != 0L) {
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 != 0L) {
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
} else {
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)

View File

@@ -101,7 +101,7 @@ class NumericUtils (val context: Context) {
else -> ""
})
}
return formatted.toString()
return formatted.toString().trim()
}
/**

View File

@@ -1,5 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
<path android:fillColor="@android:color/white" android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>

View File

@@ -177,15 +177,16 @@
android:gravity="center_horizontal"
android:textSize="30sp"/>
<ImageView
<TextView
android:id="@+id/button_more"
android:layout_width="60dp"
android:layout_height="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="0"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
android:src="@drawable/ic_more"
android:textSize="30sp"
android:text="☰"
app:tint="@android:color/darker_gray"/>
</LinearLayout>

View File

@@ -1,57 +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:orientation="horizontal">
<Spinner
android:id="@+id/type_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1" />
<!--
<Spinner
android:id="@+id/data_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/>
-->
<Spinner
android:id="@+id/time_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/>
</LinearLayout>
</LinearLayout>

View File

@@ -7,46 +7,23 @@
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/dialog_date_picker_begin"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="10dp"/>
<TextView
android:id="@+id/dialog_date_duration"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
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:id="@+id/dialog_date_picker_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"

View File

@@ -10,17 +10,7 @@
android:layout_width="match_parent"
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:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="📊 Statistics"/>
<!-- Other buttons are inserted dynamically -->
<!-- Buttons are inserted dynamically -->
</LinearLayout>
</ScrollView>

View File

@@ -18,13 +18,6 @@
<string name="event_colic_desc">Blähungskolik</string>
<string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Gewicht</string>
<string name="overflow_event_medicine">💊 Medikament</string>
<string name="overflow_event_enema">🪠 Einlauf</string>
<string name="overflow_event_note">📝 Notiz</string>
<string name="overflow_event_temperature">🌡️ Temperatur</string>
<string name="overflow_event_colic">💨 Blähungskolik</string>
<string name="toast_event_added">Ereignis gespeichert</string>
<string name="toast_logbook_saved">Logbuch gespeichert</string>
<string name="toast_event_add_error">Ereignis konnte nicht protokolliert werden</string>
@@ -42,7 +35,6 @@
<string name="no_connection_retry">Erneut versuchen</string>
<string name="settings_title">Einstellungen</string>
<string name="settings_no_breastfeeding">Kein Stillen</string>
<string name="settings_storage">Speicherort für Daten auswählen</string>
<string name="settings_storage_local">Auf dem Gerät</string>
<string name="settings_storage_local_desc">Datenschutzfreundlichste Lösung: Deine Daten verlassen dein Gerät nicht</string>

View File

@@ -18,13 +18,6 @@
<string name="event_colic_desc">Colique gazeuse</string>
<string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Poids</string>
<string name="overflow_event_medicine">💊 Médicament</string>
<string name="overflow_event_enema">🪠 Lavement</string>
<string name="overflow_event_note">📝 Note</string>
<string name="overflow_event_temperature">🌡️ Température</string>
<string name="overflow_event_colic">💨 Colique gazeuse</string>
<string name="toast_event_added">Entrée ajoutée</string>
<string name="toast_logbook_saved">Journal ajouté</string>
<string name="toast_event_add_error">Impossible d\'enregistrer cette entrée</string>

View File

@@ -3,13 +3,6 @@
<string name="title">🌜 LunaTracker 🌛</string>
<string name="logbook">Diario di bordo</string>
<string name="overflow_event_weight">⚖️ Peso</string>
<string name="overflow_event_medicine">💊 Medicina</string>
<string name="overflow_event_enema">🪠 Clistere</string>
<string name="overflow_event_note">📝 Nota</string>
<string name="overflow_event_temperature">🌡️ Temperatura</string>
<string name="overflow_event_colic">💨 Colichette</string>
<string name="event_bottle_desc">Biberon</string>
<string name="event_food_desc">Cibo</string>
<string name="event_weight_desc">Pesata</string>

View File

@@ -6,45 +6,4 @@
<item>@string/amount_normal</item>
<item>@string/amount_plenty</item>
</string-array>
<string-array name="StatisticsTypeLabels">
<item>BOTTLE_EVENTS</item>
<item>BOTTLE_SUM</item>
<item>BOTTLE_SUM_AVERAGE</item>
<item>SLEEP_SUM_AVERAGE</item>
<item>SLEEP_EVENTS</item>
<item>SLEEP_PATTERN</item>
</string-array>
<string-array name="StatisticsTypeValues">
<item>BOTTLE_EVENTS</item>
<item>BOTTLE_SUM</item>
<item>BOTTLE_SUM_AVERAGE</item>
<item>SLEEP_SUM_AVERAGE</item>
<item>SLEEP_EVENTS</item>
<item>SLEEP_PATTERN</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

@@ -88,8 +88,6 @@
<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_dynamic_menu">Dynamic Menu</string>
<string name="settings_dynamic_menu_desc">Populate the header menu with the most used events.</string>
<string name="settings_title">Settings</string>

View File

@@ -9,8 +9,6 @@ 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"
sardineAndroid = "v0.9"
@@ -33,8 +31,6 @@ 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" }
sardine-android = { module = "com.github.thegrizzlylabs:sardine-android", version.ref = "sardineAndroid" }
[plugins]