4 Commits

17 changed files with 511 additions and 841 deletions

View File

@@ -6,12 +6,12 @@ plugins {
android { android {
namespace = "it.danieleverducci.lunatracker" namespace = "it.danieleverducci.lunatracker"
compileSdk = 36 compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "it.danieleverducci.lunatracker" applicationId = "it.danieleverducci.lunatracker"
minSdk = 21 minSdk = 21
targetSdk = 36 targetSdk = 34
versionCode = 7 versionCode = 7
versionName = "0.9" versionName = "0.9"
@@ -31,12 +31,6 @@ android {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11
} }
dependenciesInfo {
// Disables dependency metadata when building APKs.
includeInApk = false
// Disables dependency metadata when building Android App Bundles.
includeInBundle = false
}
kotlinOptions { kotlinOptions {
jvmTarget = "11" jvmTarget = "11"
} }
@@ -46,6 +40,7 @@ android {
} }
dependencies { dependencies {
implementation(libs.androidx.core.ktx) implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx) implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
@@ -56,7 +51,7 @@ dependencies {
implementation(libs.androidx.material3) implementation(libs.androidx.material3)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.recyclerview) implementation(libs.androidx.recyclerview)
implementation(libs.sardine.android) implementation("com.github.thegrizzlylabs:sardine-android:v0.9")
implementation(libs.material) implementation(libs.material)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)

View File

@@ -15,7 +15,6 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.Button import android.widget.Button
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.Spinner import android.widget.Spinner
@@ -54,7 +53,6 @@ class MainActivity : AppCompatActivity() {
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
// list of all events // list of all events
var allEvents = arrayListOf<LunaEvent>() var allEvents = arrayListOf<LunaEvent>()
var currentPopupItems = listOf<LunaEvent.Type>()
} }
var logbook: Logbook? = null var logbook: Logbook? = null
@@ -86,13 +84,31 @@ class MainActivity : AppCompatActivity() {
recyclerView = findViewById(R.id.list_events) recyclerView = findViewById(R.id.list_events)
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext)) recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
populateHeaderMenu()
// Set listeners // Set listeners
findViewById<View>(R.id.logbooks_add_button).setOnClickListener { findViewById<View>(R.id.logbooks_add_button).setOnClickListener {
showAddLogbookDialog(true) showAddLogbookDialog(true)
} }
findViewById<View>(R.id.button_bottle).setOnClickListener {
addBabyBottleEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE))
}
findViewById<View>(R.id.button_food).setOnClickListener {
addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD))
}
findViewById<View>(R.id.button_nipple_left).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE))
}
findViewById<View>(R.id.button_nipple_both).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE))
}
findViewById<View>(R.id.button_nipple_right).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE))
}
findViewById<View>(R.id.button_change_poo).setOnClickListener {
addAmountEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_POO))
}
findViewById<View>(R.id.button_change_pee).setOnClickListener {
addAmountEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_PEE))
}
val moreButton = findViewById<View>(R.id.button_more) val moreButton = findViewById<View>(R.id.button_more)
moreButton.setOnClickListener { moreButton.setOnClickListener {
showOverflowPopupWindow(moreButton) showOverflowPopupWindow(moreButton)
@@ -108,11 +124,7 @@ class MainActivity : AppCompatActivity() {
loadLogbookList() loadLogbookList()
} }
findViewById<View>(R.id.button_sync).setOnClickListener { findViewById<View>(R.id.button_sync).setOnClickListener {
if (logbook != null) { loadLogbookList()
loadLogbook(logbook!!.name)
} else {
loadLogbookList()
}
} }
} }
@@ -138,82 +150,6 @@ class MainActivity : AppCompatActivity() {
allEvents = logbook?.logs ?: arrayListOf() allEvents = logbook?.logs ?: arrayListOf()
setListAdapter(allEvents) setListAdapter(allEvents)
populateHeaderMenu()
}
private fun populateHeaderMenu() {
val settingsRepository = LocalSettingsRepository(this)
val dynamicMenu = settingsRepository.loadDynamicMenu()
val eventTypeStats = mutableMapOf<LunaEvent.Type, Int>()
if (dynamicMenu) {
val sampleSize = 100
// populate frequency map from first 100 events
allEvents.take(sampleSize.coerceAtMost(allEvents.size)).forEach {
eventTypeStats[it.type] = 1 + (eventTypeStats[it.type] ?: 0)
}
}
// sort all event types by frequency or ordinal
val eventTypesSorted = LunaEvent.Type.entries.toList().sortedWith(
compareBy({ -1 * (eventTypeStats[it] ?: 0) }, { it.ordinal })
).filter { it != LunaEvent.Type.UNKNOWN }
fun setupMenu(maxButtonCount: Int, sortedEventTypes: List<LunaEvent.Type>): Int {
val row1 = findViewById<View>(R.id.linear_layout_row1)
val row1Button1 = findViewById<TextView>(R.id.button1_row1)
val row1Button2 = findViewById<TextView>(R.id.button2_row1)
val row2 = findViewById<View>(R.id.linear_layout_row2)
val row2Button1 = findViewById<TextView>(R.id.button1_row2)
val row2Button2 = findViewById<TextView>(R.id.button2_row2)
val row2Button3 = findViewById<TextView>(R.id.button3_row2)
val row3 = findViewById<View>(R.id.linear_layout_row3)
val row3Button1 = findViewById<TextView>(R.id.button1_row3)
val row3Button2 = findViewById<TextView>(R.id.button2_row3)
// hide all rows/buttons (except row 3)
for (view in listOf(row1, row1Button1, row1Button2,
row2, row2Button1, row2Button2, row2Button3,
row3, row3Button1, row3Button2)) {
view.visibility = View.GONE
}
row3.visibility = View.VISIBLE
var showCounter = 0
fun show(vararg tvs: TextView) {
for (tv in tvs) {
val type = sortedEventTypes[showCounter]
tv.text = LunaEvent.getHeaderEmoji(applicationContext, type)
tv.setOnClickListener { showCreateDialog(type) }
tv.visibility = View.VISIBLE
// show parent row
(tv.parent as View).visibility = View.VISIBLE
showCounter += 1
}
}
when (maxButtonCount) {
0 -> { } // ignore - show empty row3
1 -> show(row3Button1)
2 -> show(row3Button1, row3Button2)
3 -> show(row1Button1, row3Button1)
4, 5, 6 -> show(row1Button1, row1Button2, row3Button1, row3Button2)
else -> show(row1Button1, row1Button2, row2Button1, row2Button2, row2Button3, row3Button1, row3Button2)
}
return showCounter
}
val usedEventCount = eventTypeStats.count { it.value > 0 }
val maxButtonCount = if (dynamicMenu) { usedEventCount } else { 7 }
val eventsShown = setupMenu(maxButtonCount, eventTypesSorted)
// store left over events for popup menu
currentPopupItems = eventTypesSorted.subList(eventsShown, eventTypesSorted.size)
} }
override fun onStart() { override fun onStart() {
@@ -236,6 +172,12 @@ class MainActivity : AppCompatActivity() {
signature = settingsRepository.loadSignature() signature = settingsRepository.loadSignature()
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) {
true -> View.GONE
false -> View.VISIBLE
}
// Update list dates // Update list dates
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
@@ -264,14 +206,14 @@ class MainActivity : AppCompatActivity() {
fun askBabyBottleContent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { fun askBabyBottleContent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_bottle, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_bottle, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.dialog_number_picker) val numberPicker = dialogView.findViewById<NumberPicker>(R.id.dialog_number_picker)
numberPicker.minValue = 1 // "10" numberPicker.minValue = 1 // "10"
numberPicker.maxValue = 34 // "340 numberPicker.maxValue = 25 // "250
numberPicker.displayedValues = ((10..340 step 10).map { it.toString() }.toTypedArray()) numberPicker.displayedValues = ((10..250 step 10).map { it.toString() }.toTypedArray())
numberPicker.wrapSelectorWheel = false numberPicker.wrapSelectorWheel = false
numberPicker.value = event.quantity / 10 numberPicker.value = event.quantity / 10
@@ -306,7 +248,7 @@ class MainActivity : AppCompatActivity() {
// Show number picker dialog // Show number picker dialog
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_weight, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_weight, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
@@ -350,12 +292,12 @@ class MainActivity : AppCompatActivity() {
// Show number picker dialog // Show number picker dialog
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_temperature, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_temperature, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
val tempSlider = dialogView.findViewById<Slider>(R.id.dialog_temperature_value) val tempSlider = dialogView.findViewById<Slider>(R.id.dialog_temperature_value)
val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.Type.TEMPERATURE)!! val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.TYPE_TEMPERATURE)!!
tempSlider.valueFrom = range.first.toFloat() tempSlider.valueFrom = range.first.toFloat()
tempSlider.valueTo = range.second.toFloat() tempSlider.valueTo = range.second.toFloat()
tempSlider.value = if (event.quantity == 0) { tempSlider.value = if (event.quantity == 0) {
@@ -438,7 +380,7 @@ class MainActivity : AppCompatActivity() {
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) { fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
@@ -454,8 +396,8 @@ class MainActivity : AppCompatActivity() {
var duration = event.quantity var duration = event.quantity
fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean { fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean {
val now = System.currentTimeMillis() / 1000 val now = Calendar.getInstance().time.time / 1000
return (timeSeconds + durationSeconds) <= now && durationSeconds < (24 * 60 * 60) return (timeSeconds + durationSeconds) <= now && durationSeconds < (12 * 60 * 60)
} }
val onDateChange = { time: Long -> val onDateChange = { time: Long ->
@@ -488,7 +430,7 @@ class MainActivity : AppCompatActivity() {
durationPlus5Button.setOnClickListener { adjust(5) } durationPlus5Button.setOnClickListener { adjust(5) }
durationNowButton.setOnClickListener { durationNowButton.setOnClickListener {
val now = System.currentTimeMillis() / 1000 val now = Calendar.getInstance().time.time / 1000
val start = pickedDateTime.time.time / 1000 val start = pickedDateTime.time.time / 1000
if (now > start) { if (now > start) {
duration = (now - start).toInt() duration = (now - start).toInt()
@@ -499,6 +441,7 @@ class MainActivity : AppCompatActivity() {
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val time = pickedDateTime.time.time / 1000 val time = pickedDateTime.time.time / 1000
if (isValidTime(time, duration)) { if (isValidTime(time, duration)) {
event.time = time event.time = time
event.quantity = duration event.quantity = duration
@@ -524,7 +467,7 @@ class MainActivity : AppCompatActivity() {
fun askAmountValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { fun askAmountValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_amount, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_amount, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
@@ -566,7 +509,7 @@ class MainActivity : AppCompatActivity() {
fun askDateValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { fun askDateValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_plain, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_plain, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
@@ -595,11 +538,11 @@ class MainActivity : AppCompatActivity() {
} }
fun askNotes(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { fun askNotes(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val useQuantity = (event.type != LunaEvent.Type.NOTE) val useQuantity = (event.type != LunaEvent.TYPE_NOTE && event.type != LunaEvent.TYPE_CUSTOM)
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_notes, null) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_notes, null)
d.setTitle(event.getDialogTitle(this)) d.setTitle(event.getTypeDescription(this))
d.setMessage(event.getDialogMessage(this)) d.setMessage(event.getDialogMessage(this))
d.setView(dialogView) d.setView(dialogView)
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext) val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
@@ -777,8 +720,8 @@ class MainActivity : AppCompatActivity() {
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.getHeaderEmoji(this) emojiTextView.text = event.getTypeEmoji(this)
descriptionTextView.text = event.getDialogTitle(this) descriptionTextView.text = event.getTypeDescription(this)
d.setView(dialogView) d.setView(dialogView)
@@ -817,7 +760,7 @@ class MainActivity : AppCompatActivity() {
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) { if (event.type == LunaEvent.TYPE_SLEEP && event.quantity > 0) {
dateEndTextView.text = DateUtils.formatDateTime(event.time + event.quantity) dateEndTextView.text = DateUtils.formatDateTime(event.time + event.quantity)
dateEndTextView.visibility = View.VISIBLE dateEndTextView.visibility = View.VISIBLE
} else { } else {
@@ -839,28 +782,22 @@ class MainActivity : AppCompatActivity() {
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)
LunaEvent.Type.WEIGHT -> askWeightValue(event, false, updateValues) LunaEvent.TYPE_WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.Type.DIAPERCHANGE_POO, LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.Type.DIAPERCHANGE_PEE, LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues) LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.Type.TEMPERATURE -> askTemperatureValue(event, false, updateValues) LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues) LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.SLEEP -> askSleepValue(event, updateValues) LunaEvent.TYPE_SLEEP -> askSleepValue(event, updateValues)
else -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
} }
} }
notesTextView.setOnClickListener { notesTextView.setOnClickListener {
when (event.type) { when (event.type) {
LunaEvent.Type.FOOD, LunaEvent.TYPE_FOOD,
LunaEvent.Type.MEDICINE, LunaEvent.TYPE_MEDICINE,
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues) LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
else -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
} }
} }
@@ -875,7 +812,7 @@ class MainActivity : AppCompatActivity() {
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)
if (previousEvent != null) { if (previousEvent != null) {
val emoji = previousEvent.getHeaderEmoji(applicationContext) val emoji = previousEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time) val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
previousTextView.text = String.format("⬅️ %s %s", emoji, time) previousTextView.text = String.format("⬅️ %s %s", emoji, time)
previousTextView.setOnClickListener { previousTextView.setOnClickListener {
@@ -890,7 +827,7 @@ class MainActivity : AppCompatActivity() {
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next) val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next)
val nextEvent = getNextSameEvent(event, allEvents) val nextEvent = getNextSameEvent(event, allEvents)
if (nextEvent != null) { if (nextEvent != null) {
val emoji = nextEvent.getHeaderEmoji(applicationContext) val emoji = nextEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time) val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
nextTextView.text = String.format("%s %s ➡️", time, emoji) nextTextView.text = String.format("%s %s ➡️", time, emoji)
nextTextView.setOnClickListener { nextTextView.setOnClickListener {
@@ -1061,7 +998,7 @@ class MainActivity : AppCompatActivity() {
if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) { if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) {
for (e in logbook?.logs ?: listOf()) { for (e in logbook?.logs ?: listOf()) {
val em = e.getHeaderEmoji(this@MainActivity) val em = e.getTypeEmoji(this@MainActivity)
if (em == getString(R.string.event_unknown_type)) { if (em == getString(R.string.event_unknown_type)) {
Log.e(TAG, "UNKNOWN: ${e.type}") Log.e(TAG, "UNKNOWN: ${e.type}")
} }
@@ -1246,29 +1183,6 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun showCreateDialog(type: LunaEvent.Type) {
val event = LunaEvent(type)
when (type) {
LunaEvent.Type.BABY_BOTTLE -> addBabyBottleEvent(event)
LunaEvent.Type.WEIGHT -> addWeightEvent(event)
LunaEvent.Type.BREASTFEEDING_LEFT_NIPPLE -> addPlainEvent(event)
LunaEvent.Type.BREASTFEEDING_BOTH_NIPPLE -> addPlainEvent(event)
LunaEvent.Type.BREASTFEEDING_RIGHT_NIPPLE -> addPlainEvent(event)
LunaEvent.Type.DIAPERCHANGE_POO -> addAmountEvent(event)
LunaEvent.Type.DIAPERCHANGE_PEE -> addAmountEvent(event)
LunaEvent.Type.MEDICINE -> addNoteEvent(event)
LunaEvent.Type.ENEMA -> addNoteEvent(event)
LunaEvent.Type.NOTE -> addNoteEvent(event)
LunaEvent.Type.COLIC -> addPlainEvent(event)
LunaEvent.Type.TEMPERATURE -> addTemperatureEvent(event)
LunaEvent.Type.FOOD -> addNoteEvent(event)
LunaEvent.Type.PUKE -> addAmountEvent(event)
LunaEvent.Type.BATH -> addPlainEvent(event)
LunaEvent.Type.SLEEP -> addSleepEvent(event)
LunaEvent.Type.UNKNOWN -> {} // ignore
}
}
private fun showOverflowPopupWindow(anchor: View) { private fun showOverflowPopupWindow(anchor: View) {
if (showingOverflowPopupWindow) if (showingOverflowPopupWindow)
return return
@@ -1277,8 +1191,42 @@ class MainActivity : AppCompatActivity() {
isOutsideTouchable = true isOutsideTouchable = true
val inflater = LayoutInflater.from(anchor.context) val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.more_events_popup, null) contentView = inflater.inflate(R.layout.more_events_popup, null)
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
// Add statistics (hard coded) addNoteEvent(LunaEvent(LunaEvent.TYPE_MEDICINE))
dismiss()
}
contentView.findViewById<View>(R.id.button_enema).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
dismiss()
}
contentView.findViewById<View>(R.id.button_note).setOnClickListener {
addNoteEvent(LunaEvent(LunaEvent.TYPE_NOTE))
dismiss()
}
contentView.findViewById<View>(R.id.button_temperature).setOnClickListener {
addTemperatureEvent(LunaEvent(LunaEvent.TYPE_TEMPERATURE))
dismiss()
}
contentView.findViewById<View>(R.id.button_puke).setOnClickListener {
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()
}
contentView.findViewById<View>(R.id.button_scale).setOnClickListener {
addWeightEvent(LunaEvent(LunaEvent.TYPE_WEIGHT))
dismiss()
}
contentView.findViewById<View>(R.id.button_bath).setOnClickListener {
addPlainEvent(LunaEvent(LunaEvent.TYPE_BATH))
dismiss()
}
contentView.findViewById<View>(R.id.button_statistics).setOnClickListener { contentView.findViewById<View>(R.id.button_statistics).setOnClickListener {
if (logbook != null && !pauseLogbookUpdate) { if (logbook != null && !pauseLogbookUpdate) {
val i = Intent(applicationContext, StatisticsActivity::class.java) val i = Intent(applicationContext, StatisticsActivity::class.java)
@@ -1289,20 +1237,6 @@ class MainActivity : AppCompatActivity() {
} }
dismiss() dismiss()
} }
val linearLayout = contentView.findViewById<LinearLayout>(R.id.layout_list)
// Add buttons to create other events
for (type in currentPopupItems) {
val view = layoutInflater.inflate(R.layout.more_events_popup_item, linearLayout, false)
val textView = view.findViewById<TextView>(R.id.tv)
textView.text = LunaEvent.getPopupItemTitle(applicationContext, type)
textView.setOnClickListener {
showCreateDialog(type)
dismiss()
}
linearLayout.addView(textView)
}
}.also { popupWindow -> }.also { popupWindow ->
popupWindow.setOnDismissListener({ popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({ Handler(mainLooper).postDelayed({

View File

@@ -4,7 +4,6 @@ import android.os.Bundle
import android.view.View import android.view.View
import android.widget.EditText import android.widget.EditText
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.Spinner
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@@ -25,7 +24,7 @@ open class SettingsActivity : AppCompatActivity() {
protected lateinit var textViewWebDAVUser: TextView protected lateinit var textViewWebDAVUser: TextView
protected lateinit var textViewWebDAVPass: TextView protected lateinit var textViewWebDAVPass: TextView
protected lateinit var progressIndicator: LinearProgressIndicator protected lateinit var progressIndicator: LinearProgressIndicator
protected lateinit var switchDynamicMenu: SwitchMaterial protected lateinit var switchNoBreastfeeding: SwitchMaterial
protected lateinit var textViewSignature: EditText protected lateinit var textViewSignature: EditText
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@@ -38,7 +37,7 @@ open class SettingsActivity : AppCompatActivity() {
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user) textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass) textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
progressIndicator = findViewById(R.id.progress_indicator) progressIndicator = findViewById(R.id.progress_indicator)
switchDynamicMenu = findViewById(R.id.switch_dynamic_menu) switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
textViewSignature = findViewById(R.id.settings_signature) textViewSignature = findViewById(R.id.settings_signature)
findViewById<View>(R.id.settings_save).setOnClickListener({ findViewById<View>(R.id.settings_save).setOnClickListener({
@@ -55,7 +54,7 @@ open class SettingsActivity : AppCompatActivity() {
fun loadSettings() { fun loadSettings() {
val dataRepo = settingsRepository.loadDataRepository() val dataRepo = settingsRepository.loadDataRepository()
val webDavCredentials = settingsRepository.loadWebdavCredentials() val webDavCredentials = settingsRepository.loadWebdavCredentials()
val dynamicMenu = settingsRepository.loadDynamicMenu() val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
val signature = settingsRepository.loadSignature() val signature = settingsRepository.loadSignature()
when (dataRepo) { when (dataRepo) {
@@ -64,7 +63,7 @@ open class SettingsActivity : AppCompatActivity() {
} }
textViewSignature.setText(signature) textViewSignature.setText(signature)
switchDynamicMenu.isChecked = dynamicMenu switchNoBreastfeeding.isChecked = noBreastfeeding
if (webDavCredentials != null) { if (webDavCredentials != null) {
textViewWebDAVUrl.text = webDavCredentials[0] textViewWebDAVUrl.text = webDavCredentials[0]
@@ -161,7 +160,7 @@ open class SettingsActivity : AppCompatActivity() {
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
) )
settingsRepository.saveDynamicMenu(switchDynamicMenu.isChecked) settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
settingsRepository.saveSignature(textViewSignature.text.toString()) settingsRepository.saveSignature(textViewSignature.text.toString())
settingsRepository.saveWebdavCredentials( settingsRepository.saveWebdavCredentials(
textViewWebDAVUrl.text.toString(), textViewWebDAVUrl.text.toString(),

View File

@@ -17,39 +17,24 @@ import com.github.mikephil.charting.data.BarEntry
import com.github.mikephil.charting.formatter.ValueFormatter import com.github.mikephil.charting.formatter.ValueFormatter
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet import com.github.mikephil.charting.interfaces.datasets.IBarDataSet
import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEvent
import utils.DateUtils.Companion.formatTimeDuration
import utils.NumericUtils import utils.NumericUtils
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import androidx.core.graphics.toColorInt
class StatisticsActivity : AppCompatActivity() { class StatisticsActivity : AppCompatActivity() {
lateinit var barChart: BarChart lateinit var barChart: BarChart
lateinit var noDataTextView: TextView lateinit var noDataTextView: TextView
lateinit var graphTypeSpinner: Spinner lateinit var eventTypeSelection: Spinner
//lateinit var dataTypeSelection: Spinner lateinit var dataTypeSelection: Spinner
lateinit var timeRangeSpinner: Spinner lateinit var timeRangeSelection: 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 // default selection
var graphTypeSelection = GraphType.SLEEP_SUM var eventTypeSelectionValue = "BOTTLE"
var timeRangeSelection = RangeType.DAY var dataTypeSelectionValue = "AMOUNT"
var timeRangeSelectionValue = "DAY"
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -80,38 +65,25 @@ class StatisticsActivity : AppCompatActivity() {
barChart.xAxis.setDrawLabels(true) barChart.xAxis.setDrawLabels(true)
barChart.xAxis.setDrawAxisLine(false) barChart.xAxis.setDrawAxisLine(false)
graphTypeSpinner = findViewById(R.id.type_selection) eventTypeSelection = findViewById(R.id.type_selection)
//dataTypeSelection = findViewById(R.id.data_selection) dataTypeSelection = findViewById(R.id.data_selection)
timeRangeSpinner = findViewById(R.id.time_selection) timeRangeSelection = findViewById(R.id.time_selection)
setupSpinner("SLEEP_SUM", setupSpinner(eventTypeSelectionValue,
R.id.type_selection, R.id.type_selection,
R.array.StatisticsTypeLabels, R.array.StatisticsTypeLabels,
R.array.StatisticsTypeValues, R.array.StatisticsTypeValues,
object : SpinnerItemSelected { object : SpinnerItemSelected {
override fun call(newValue: String?) { override fun call(newValue: String?) {
newValue ?: return if (newValue != null) {
graphTypeSelection = when (newValue) { eventTypeSelectionValue = newValue
"BOTTLE_EVENTS" -> GraphType.BOTTLE_EVENTS //Log.d("event", "new value: $newValue")
"BOTTLE_SUM" -> GraphType.BOTTLE_SUM updateGraph()
"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, setupSpinner(dataTypeSelectionValue,
R.id.data_selection, R.id.data_selection,
R.array.StatisticsDataLabels, R.array.StatisticsDataLabels,
@@ -126,25 +98,18 @@ class StatisticsActivity : AppCompatActivity() {
} }
} }
) )
*/
setupSpinner("DAY", setupSpinner(timeRangeSelectionValue,
R.id.time_selection, R.id.time_selection,
R.array.StatisticsTimeLabels, R.array.StatisticsTimeLabels,
R.array.StatisticsTimeValues, R.array.StatisticsTimeValues,
object : SpinnerItemSelected { object : SpinnerItemSelected {
override fun call(newValue: String?) { override fun call(newValue: String?) {
newValue ?: return if (newValue != null) {
timeRangeSelection = when (newValue) { timeRangeSelectionValue = newValue
"DAY" -> RangeType.DAY //Log.d("event", "new value: $newValue")
"WEEK" -> RangeType.WEEK updateGraph()
"MONTH" -> RangeType.MONTH
else -> {
Log.e(TAG, "Invalid time range selection: $newValue")
return
}
} }
//Log.d("event", "new value: $newValue")
updateGraph()
} }
} }
) )
@@ -152,294 +117,123 @@ class StatisticsActivity : AppCompatActivity() {
updateGraph() updateGraph()
} }
fun showSleepBarGraph(events: List<LunaEvent>, unixToSpan: (Long) -> Int, spanToUnix: (Int) -> Long) { fun updateGraph() {
fun getEndTime(event: LunaEvent): Long { val eventType = when (eventTypeSelectionValue) {
if (event.quantity == 0) { "BOTTLE" -> LunaEvent.TYPE_BABY_BOTTLE
// sleep is still ongoing "SLEEP" -> LunaEvent.TYPE_SLEEP
val dateTime = Calendar.getInstance() else -> {
return (dateTime.time.time / 1000) - event.time Log.e(TAG, "unhandled eventTypeSelectionValue: $eventTypeSelectionValue")
} else { return
return event.quantity.toLong() }
}
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
} }
} }
data class SleepRange(val start: Long, val end: Long) val spanToUnix = when (timeRangeSelectionValue) {
val ranges = arrayListOf<SleepRange>() "DAY" -> { span: Int -> daysToUnix(span) }
"WEEK" -> { span: Int -> weeksToUnix(span) }
// Transform events into time ranges. "MONTH" -> { span: Int -> monthsToUnix(span) }
// Merge overlapping times and extend else -> {
// ongoing sleep events until now. Log.e(TAG, "Invalid timeRangeSelectionValue: $timeRangeSelectionValue")
val dateTime = Calendar.getInstance() return
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()) { fun spanToLabel(span: Int): String {
val lastItem = ranges.lastItem() val dateTime = Calendar.getInstance()
if (lastItem.start) dateTime.time = Date(1000 * spanToUnix(span))
if (lastItem.end <= event.time) { val year = dateTime.get(Calendar.YEAR)
// distinct 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
} }
}
}
*/
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 { } else {
Log.e(TAG, "Invalid remaining sleep duration, exceeds day/week or month bounds => ignore") val index = unixToSpan(event.time) - startSpan
values[index].y += 1
} }
} }
}
*/
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 { } else {
set1.setDrawValues(false) barChart.visibility = View.GONE
noDataTextView.visibility = View.VISIBLE
} }
set1.setDrawIcons(false)
barChart.legend.isEnabled = false
barChart.xAxis.setLabelCount(min(values.size, 24)) 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, "") val set1 = BarDataSet(values, "")
set1.setDrawValues(true) set1.setDrawValues(true)
set1.setDrawIcons(false) 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?>() val dataSets = ArrayList<IBarDataSet?>()
dataSets.add(set1) dataSets.add(set1)
@@ -448,11 +242,17 @@ class StatisticsActivity : AppCompatActivity() {
data.setValueFormatter(object : ValueFormatter() { data.setValueFormatter(object : ValueFormatter() {
override fun getFormattedValue(value: Float): String { override fun getFormattedValue(value: Float): String {
//Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}") //Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}")
return when (graphTypeSelection) { return when (dataTypeSelectionValue) {
GraphType.BOTTLE_EVENTS -> value.toInt().toString() "EVENT" -> value.toInt().toString()
GraphType.BOTTLE_SUM -> NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_BABY_BOTTLE, value.toInt()) "AMOUNT" -> when (eventTypeSelectionValue) {
else -> { "BOTTLE" -> NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_BABY_BOTTLE, value.toInt())
Log.e(TAG, "unhandled graphTypeSelection") "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() value.toInt().toString()
} }
} }
@@ -470,64 +270,6 @@ class StatisticsActivity : AppCompatActivity() {
barChart.invalidate() 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 { private interface SpinnerItemSelected {
fun call(newValue: String?) fun call(newValue: String?)
} }
@@ -542,7 +284,7 @@ class StatisticsActivity : AppCompatActivity() {
val arrayValues = resources.getStringArray(arrayValuesId) val arrayValues = resources.getStringArray(arrayValuesId)
val spinner = findViewById<Spinner>(spinnerId) val spinner = findViewById<Spinner>(spinnerId)
val spinnerAdapter = val spinnerAdapter =
ArrayAdapter.createFromResource(this, arrayId, R.layout.statistics_spinner_item) 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.adapter = spinnerAdapter
spinner.setSelection(arrayValues.indexOf(currentValue)) spinner.setSelection(arrayValues.indexOf(currentValue))
@@ -583,6 +325,7 @@ class StatisticsActivity : AppCompatActivity() {
dateTime.time = Date(0) dateTime.time = Date(0)
dateTime.set(Calendar.YEAR, months / 12) dateTime.set(Calendar.YEAR, months / 12)
dateTime.set(Calendar.MONTH, months % 12) dateTime.set(Calendar.MONTH, months % 12)
//Calendar.MONTH_OF_YEAR?
dateTime.set(Calendar.HOUR, 0) dateTime.set(Calendar.HOUR, 0)
dateTime.set(Calendar.MINUTE, 0) dateTime.set(Calendar.MINUTE, 0)
dateTime.set(Calendar.SECOND, 0) dateTime.set(Calendar.SECOND, 0)
@@ -628,19 +371,5 @@ class StatisticsActivity : AppCompatActivity() {
dateTime.set(Calendar.SECOND, 0) dateTime.set(Calendar.SECOND, 0)
return dateTime.time.time / 1000 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

@@ -52,22 +52,18 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
) )
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor)) holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
// Contents // Contents
holder.type.text = item.getHeaderEmoji(context) holder.type.text = item.getTypeEmoji(context)
holder.description.text = when (item.type) { holder.description.text = when (item.type) {
LunaEvent.Type.MEDICINE -> item.notes LunaEvent.TYPE_MEDICINE -> item.notes
LunaEvent.Type.NOTE -> item.notes LunaEvent.TYPE_NOTE -> item.notes
else -> item.getRowItemTitle(context) LunaEvent.TYPE_CUSTOM -> item.notes
else -> item.getTypeDescription(context)
} }
val endTime = if (item.type == LunaEvent.Type.SLEEP) { holder.time.text = DateUtils.formatTimeAgo(context, item.getEndTime())
item.quantity + item.time
} else {
item.time
}
holder.time.text = DateUtils.formatTimeAgo(context, endTime)
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
if (item.type == LunaEvent.Type.WEIGHT) { if (item.type == LunaEvent.TYPE_WEIGHT) {
val lastWeight = getPreviousWeightEvent(position) val lastWeight = getPreviousWeightEvent(position)
if (lastWeight != null) { if (lastWeight != null) {
val differenceInWeight = item.quantity - lastWeight.quantity val differenceInWeight = item.quantity - lastWeight.quantity
@@ -98,7 +94,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
return null return null
for (pos in startFromPosition + 1 until items.size) { for (pos in startFromPosition + 1 until items.size) {
val item = items.get(pos) val item = items.get(pos)
if (item.type != LunaEvent.Type.WEIGHT) if (item.type != LunaEvent.TYPE_WEIGHT)
continue continue
return item return item
} }

View File

@@ -12,24 +12,25 @@ import java.util.Date
* release, it is simply ignored by previous ones). * release, it is simply ignored by previous ones).
*/ */
class LunaEvent: Comparable<LunaEvent> { class LunaEvent: Comparable<LunaEvent> {
enum class Type {
BABY_BOTTLE, companion object {
FOOD, const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
BREASTFEEDING_LEFT_NIPPLE, const val TYPE_WEIGHT = "WEIGHT"
BREASTFEEDING_BOTH_NIPPLE, const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
BREASTFEEDING_RIGHT_NIPPLE, const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
DIAPERCHANGE_POO, const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
DIAPERCHANGE_PEE, const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
SLEEP, const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
WEIGHT, const val TYPE_MEDICINE = "MEDICINE"
MEDICINE, const val TYPE_ENEMA = "ENEMA"
ENEMA, const val TYPE_NOTE = "NOTE"
NOTE, const val TYPE_CUSTOM = "CUSTOM"
COLIC, const val TYPE_COLIC = "COLIC"
TEMPERATURE, const val TYPE_TEMPERATURE = "TEMPERATURE"
PUKE, const val TYPE_FOOD = "FOOD"
BATH, const val TYPE_PUKE = "PUKE"
UNKNOWN const val TYPE_BATH = "BATH"
const val TYPE_SLEEP = "SLEEP"
} }
private val jo: JSONObject private val jo: JSONObject
@@ -39,16 +40,10 @@ class LunaEvent: Comparable<LunaEvent> {
set(value) { set(value) {
jo.put("time", value) jo.put("time", value)
} }
var type: Type var type: String
get(): Type { get(): String = jo.getString("type")
return try {
Type.valueOf(jo.getString("type"))
} catch (_: Exception) {
Type.UNKNOWN
}
}
set(value) { set(value) {
jo.put("type", value.name) jo.put("type", value)
} }
var quantity: Int var quantity: Int
get() = jo.optInt("quantity") get() = jo.optInt("quantity")
@@ -86,33 +81,93 @@ class LunaEvent: Comparable<LunaEvent> {
this.signature = event.signature this.signature = event.signature
} }
constructor(type: Type) { constructor(type: String) {
this.jo = JSONObject() this.jo = JSONObject()
this.time = System.currentTimeMillis() / 1000 this.time = System.currentTimeMillis() / 1000
this.type = type this.type = type
} }
constructor(type: Type, quantity: Int) { constructor(type: String, quantity: Int) {
this.jo = JSONObject() this.jo = JSONObject()
this.time = System.currentTimeMillis() / 1000 this.time = System.currentTimeMillis() / 1000
this.type = type this.type = type
this.quantity = quantity this.quantity = quantity
} }
fun getHeaderEmoji(context: Context): String { fun getStartTime(): Long {
return getHeaderEmoji(context, type) return time
} }
fun getDialogTitle(context: Context): String { fun getEndTime(): Long {
return getDialogTitle(context, type) return if (type == TYPE_SLEEP) {
time + quantity
} else {
time
}
} }
fun getRowItemTitle(context: Context): String { fun getTypeEmoji(context: Context): String {
return getPopupItemTitle(context, type).split(" ", limit = 2).last() // remove emoji return context.getString(
when (type) {
TYPE_BABY_BOTTLE -> R.string.event_bottle_type
TYPE_WEIGHT -> R.string.event_weight_type
TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
TYPE_MEDICINE -> R.string.event_medicine_type
TYPE_ENEMA -> R.string.event_enema_type
TYPE_NOTE -> R.string.event_note_type
TYPE_TEMPERATURE -> R.string.event_temperature_type
TYPE_COLIC -> R.string.event_colic_type
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
}
)
} }
fun getDialogMessage(context: Context): String { fun getTypeDescription(context: Context): String {
return getDialogMessage(context, type) return context.getString(
when (type) {
TYPE_BABY_BOTTLE -> R.string.event_bottle_desc
TYPE_WEIGHT -> R.string.event_weight_desc
TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc
TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc
TYPE_MEDICINE -> R.string.event_medicine_desc
TYPE_ENEMA -> R.string.event_enema_desc
TYPE_NOTE -> R.string.event_note_desc
TYPE_TEMPERATURE -> R.string.event_temperature_desc
TYPE_COLIC -> R.string.event_colic_desc
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
}
)
}
fun getDialogMessage(context: Context): String? {
return context.getString(
when(type) {
TYPE_BABY_BOTTLE -> R.string.log_bottle_dialog_description
TYPE_MEDICINE -> R.string.log_medicine_dialog_description
TYPE_TEMPERATURE -> R.string.log_temperature_dialog_description
TYPE_DIAPERCHANGE_POO,
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
}
)
} }
fun toJson(): JSONObject { fun toJson(): JSONObject {
@@ -126,95 +181,4 @@ class LunaEvent: Comparable<LunaEvent> {
override fun compareTo(other: LunaEvent): Int { override fun compareTo(other: LunaEvent): Int {
return (this.time - other.time).toInt() return (this.time - other.time).toInt()
} }
}
companion object {
fun getHeaderEmoji(context: Context, type: Type): String {
return context.getString(
when (type) {
Type.BABY_BOTTLE -> R.string.event_bottle_type
Type.WEIGHT -> R.string.event_weight_type
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
Type.DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
Type.DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
Type.MEDICINE -> R.string.event_medicine_type
Type.ENEMA -> R.string.event_enema_type
Type.NOTE -> R.string.event_note_type
Type.TEMPERATURE -> R.string.event_temperature_type
Type.COLIC -> R.string.event_colic_type
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
Type.UNKNOWN -> R.string.event_unknown_type
}
)
}
fun getDialogMessage(context: Context, type: Type): String {
return context.getString(
when (type) {
Type.BABY_BOTTLE -> R.string.log_bottle_dialog_description
Type.MEDICINE -> R.string.log_medicine_dialog_description
Type.TEMPERATURE -> R.string.log_temperature_dialog_description
Type.DIAPERCHANGE_POO,
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
}
)
}
fun getDialogTitle(context: Context, type: Type): String {
return context.getString(
when (type) {
Type.BABY_BOTTLE -> R.string.event_bottle_desc
Type.WEIGHT -> R.string.event_weight_desc
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
Type.DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc
Type.DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc
Type.MEDICINE -> R.string.event_medicine_desc
Type.ENEMA -> R.string.event_enema_desc
Type.NOTE -> R.string.event_note_desc
Type.TEMPERATURE -> R.string.event_temperature_desc
Type.COLIC -> R.string.event_colic_desc
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
Type.UNKNOWN -> R.string.event_unknown_desc
}
)
}
// Entries for for popup list
fun getPopupItemTitle(context: Context, type: Type): String {
return context.getString(
when (type) {
Type.BABY_BOTTLE -> R.string.event_type_item_bottle
Type.WEIGHT -> R.string.event_type_item_weight
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_type_item_breastfeeding_left
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_type_item_breastfeeding_both
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_type_item_breastfeeding_right
Type.DIAPERCHANGE_POO -> R.string.event_type_item_diaperchange_poo
Type.DIAPERCHANGE_PEE -> R.string.event_type_item_diaperchange_pee
Type.MEDICINE -> R.string.event_type_item_medicine
Type.ENEMA -> R.string.event_type_item_enema
Type.NOTE -> R.string.event_type_item_note
Type.TEMPERATURE -> R.string.event_type_item_temperature
Type.COLIC -> R.string.event_type_item_colic
Type.FOOD -> R.string.event_type_item_food
Type.PUKE -> R.string.event_type_item_puke
Type.BATH -> R.string.event_type_item_bath
Type.SLEEP -> R.string.event_type_item_sleep
Type.UNKNOWN -> R.string.event_type_item_unknown
}
)
}
}
}

View File

@@ -13,7 +13,7 @@ class LocalSettingsRepository(val context: Context) {
const val SHARED_PREFS_DAV_URL = "webdav_url" const val SHARED_PREFS_DAV_URL = "webdav_url"
const val SHARED_PREFS_DAV_USER = "webdav_user" const val SHARED_PREFS_DAV_USER = "webdav_user"
const val SHARED_PREFS_DAV_PASS = "webdav_password" const val SHARED_PREFS_DAV_PASS = "webdav_password"
const val SHARED_PREFS_DYNAMIC_MENU = "dynamic_menu" const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
const val SHARED_PREFS_SIGNATURE = "signature" const val SHARED_PREFS_SIGNATURE = "signature"
} }
enum class DATA_REPO {LOCAL_FILE, WEBDAV} enum class DATA_REPO {LOCAL_FILE, WEBDAV}
@@ -31,12 +31,12 @@ class LocalSettingsRepository(val context: Context) {
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: "" return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
} }
fun saveDynamicMenu(content: Boolean) { fun saveNoBreastfeeding(content: Boolean) {
sharedPreferences.edit { putBoolean(SHARED_PREFS_DYNAMIC_MENU, content) } sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
} }
fun loadDynamicMenu(): Boolean { fun loadNoBreastfeeding(): Boolean {
return sharedPreferences.getBoolean(SHARED_PREFS_DYNAMIC_MENU, false) return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false)
} }
fun saveDataRepository(repo: DATA_REPO) { fun saveDataRepository(repo: DATA_REPO) {

View File

@@ -66,51 +66,51 @@ class NumericUtils (val context: Context) {
return formatEventQuantity(event.type, event.quantity) return formatEventQuantity(event.type, event.quantity)
} }
fun formatEventQuantity(type: LunaEvent.Type, quantity: Int): String { fun formatEventQuantity(type: String, quantity: Int): String {
val formatted = StringBuilder() val formatted = StringBuilder()
if (quantity > 0) { if (quantity > 0) {
formatted.append(when (type) { formatted.append(when (type) {
LunaEvent.Type.TEMPERATURE -> LunaEvent.TYPE_TEMPERATURE ->
(quantity / 10.0f).toString() (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(quantity) {
Log.e("NumericUtils", "Invalid index $quantity") Log.e("NumericUtils", "Invalid index $quantity")
return "" return ""
} }
} }
LunaEvent.Type.SLEEP -> formatTimeDuration(context, quantity.toLong()) LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, quantity.toLong())
else -> quantity else -> quantity
}) })
formatted.append(" ") formatted.append(" ")
formatted.append( formatted.append(
when (type) { when (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
LunaEvent.Type.TEMPERATURE -> measurement_unit_temperature_base LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
else -> "" else -> ""
} }
) )
} else { } else {
formatted.append(when (type) { formatted.append(when (type) {
LunaEvent.Type.SLEEP -> "💤" // baby is sleeping LunaEvent.TYPE_SLEEP -> "💤" // baby is sleeping
else -> "" else -> ""
}) })
} }
return formatted.toString().trim() return formatted.toString()
} }
/** /**
* Returns a valid quantity range for the event type. * Returns a valid quantity range for the event type.
* @return min, max, normal * @return min, max, normal
*/ */
fun getValidEventQuantityRange(lunaEventType: LunaEvent.Type): Triple<Int, Int, Int>? { fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
return when (lunaEventType) { return when (lunaEventType) {
LunaEvent.Type.TEMPERATURE -> { LunaEvent.TYPE_TEMPERATURE -> {
if (isMetricSystem()) if (isMetricSystem())
Triple( Triple(
context.resources.getInteger(R.integer.human_body_temp_min_metric), context.resources.getInteger(R.integer.human_body_temp_min_metric),

View File

@@ -86,16 +86,15 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/linear_layout_row1"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal"> android:orientation="horizontal">
<TextView <TextView
android:id="@+id/button1_row1" android:id="@+id/button_bottle"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="2"
android:layout_margin="5dp" android:layout_margin="5dp"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
@@ -103,7 +102,7 @@
android:text="@string/event_bottle_type"/> android:text="@string/event_bottle_type"/>
<TextView <TextView
android:id="@+id/button2_row1" android:id="@+id/button_food"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
@@ -116,73 +115,77 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/linear_layout_row2"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content"
android:id="@+id/layout_nipples">
<TextView <TextView
android:id="@+id/button1_row2" android:id="@+id/button_nipple_left"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30sp"/> android:textSize="30sp"
android:text="🤱⬅️"/>
<TextView <TextView
android:id="@+id/button2_row2" android:id="@+id/button_nipple_both"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30sp"/> android:textSize="30sp"
android:text="🤱↔️"/>
<TextView <TextView
android:id="@+id/button3_row2" android:id="@+id/button_nipple_right"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="1" android:layout_weight="1"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30sp"/> android:textSize="30sp"
android:text="🤱➡️️"/>
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/linear_layout_row3"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<TextView <TextView
android:id="@+id/button1_row3" android:id="@+id/button_change_poo"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="2" android:layout_weight="2"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30sp"/> android:textSize="30sp"
android:text="🚼 💩"/>
<TextView <TextView
android:id="@+id/button2_row3" android:id="@+id/button_change_pee"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="2" android:layout_weight="2"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30sp"/> android:textSize="30sp"
android:text="🚼 💧"/>
<ImageView <ImageView
android:id="@+id/button_more" android:id="@+id/button_more"
android:layout_width="60dp" android:layout_width="0dp"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_margin="5dp" android:layout_margin="5dp"
android:layout_weight="0" android:layout_weight="1"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:src="@drawable/ic_more" android:src="@drawable/ic_more"

View File

@@ -137,7 +137,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="30dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:text="@string/settings_signature_desc"/> android:text="@string/settings_signature_desc"/>
@@ -149,13 +149,13 @@
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:layout_height="wrap_content"
android:textStyle="bold" android:textStyle="bold"
android:text="@string/settings_dynamic_menu" /> android:text="@string/settings_no_breastfeeding" />
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_dynamic_menu" android:id="@+id/switch_no_breastfeeding"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="20dp" android:layout_marginEnd="20dp"
@@ -166,9 +166,9 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="20dp" android:layout_marginStart="30dp"
android:layout_marginTop="5dp" android:layout_marginTop="5dp"
android:text="@string/settings_dynamic_menu_desc"/> android:text="@string/settings_no_breastfeeding_desc"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -29,27 +29,25 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="0" android:layout_weight="0"
android:layout_marginTop="10dp"
android:orientation="horizontal"> android:orientation="horizontal">
<Spinner <Spinner
android:id="@+id/type_selection" android:id="@+id/type_selection"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1" /> android:layout_weight="1" />
<!--
<Spinner <Spinner
android:id="@+id/data_selection" android:id="@+id/data_selection"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/> android:layout_weight="1"/>
-->
<Spinner <Spinner
android:id="@+id/time_selection" android:id="@+id/time_selection"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center"
android:layout_weight="1"/> android:layout_weight="1"/>
</LinearLayout> </LinearLayout>

View File

@@ -6,7 +6,6 @@
android:background="@color/transparent"> android:background="@color/transparent">
<LinearLayout <LinearLayout
android:id="@+id/layout_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="vertical"> android:orientation="vertical">
@@ -15,12 +14,102 @@
android:id="@+id/button_statistics" android:id="@+id/button_statistics"
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"
android:text="📊 Statistics"/> android:text="📊 Statistics"/>
<!-- Other buttons are inserted dynamically --> <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"
android:text="@string/overflow_event_medicine"/>
<TextView
android:id="@+id/button_note"
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_note"/>
<TextView
android:id="@+id/button_temperature"
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_temperature"/>
<TextView
android:id="@+id/button_puke"
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_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"
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_colic"/>
<TextView
android:id="@+id/button_scale"
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_weight"/>
<TextView
android:id="@+id/button_bath"
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_bath"/>
<TextView
android:id="@+id/button_enema"
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_enema"/>
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>

View File

@@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/tv"
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="Item Template"/>

View File

@@ -8,23 +8,15 @@
</string-array> </string-array>
<string-array name="StatisticsTypeLabels"> <string-array name="StatisticsTypeLabels">
<item>BOTTLE_EVENTS</item> <item>Bottle</item>
<item>BOTTLE_SUM</item> <item>Sleep</item>
<item>BOTTLE_SUM_AVERAGE</item>
<item>SLEEP_SUM_AVERAGE</item>
<item>SLEEP_EVENTS</item>
<item>SLEEP_PATTERN</item>
</string-array> </string-array>
<string-array name="StatisticsTypeValues"> <string-array name="StatisticsTypeValues">
<item>BOTTLE_EVENTS</item> <item>BOTTLE</item>
<item>BOTTLE_SUM</item> <item>SLEEP</item>
<item>BOTTLE_SUM_AVERAGE</item>
<item>SLEEP_SUM_AVERAGE</item>
<item>SLEEP_EVENTS</item>
<item>SLEEP_PATTERN</item>
</string-array> </string-array>
<!--
<string-array name="StatisticsDataLabels"> <string-array name="StatisticsDataLabels">
<item>Event</item> <item>Event</item>
<item>Amount</item> <item>Amount</item>
@@ -34,7 +26,7 @@
<item>EVENT</item> <item>EVENT</item>
<item>AMOUNT</item> <item>AMOUNT</item>
</string-array> </string-array>
-->
<string-array name="StatisticsTimeLabels"> <string-array name="StatisticsTimeLabels">
<item>Day</item> <item>Day</item>
<item>Week</item> <item>Week</item>

View File

@@ -3,13 +3,12 @@
<string name="title">🌜 LunaTracker 🌛</string> <string name="title">🌜 LunaTracker 🌛</string>
<string name="logbook">Logged events</string> <string name="logbook">Logged events</string>
<!-- menu header items -->
<string name="event_bottle_type" translatable="false">🍼</string> <string name="event_bottle_type" translatable="false">🍼</string>
<string name="event_food_type" translatable="false">🥣</string> <string name="event_food_type" translatable="false">🥣</string>
<string name="event_weight_type" translatable="false">⚖️</string> <string name="event_weight_type" translatable="false">⚖️</string>
<string name="event_breastfeeding_left_type" translatable="false">🤱⬅️</string> <string name="event_breastfeeding_left_type" translatable="false">🤱</string>
<string name="event_breastfeeding_both_type" translatable="false">🤱↔</string> <string name="event_breastfeeding_both_type" translatable="false">🤱 </string>
<string name="event_breastfeeding_right_type" translatable="false">🤱➡️️</string> <string name="event_breastfeeding_right_type" translatable="false">🤱</string>
<string name="event_diaperchange_poo_type" translatable="false">🚼 💩</string> <string name="event_diaperchange_poo_type" translatable="false">🚼 💩</string>
<string name="event_diaperchange_pee_type" translatable="false">🚼 💧</string> <string name="event_diaperchange_pee_type" translatable="false">🚼 💧</string>
<string name="event_medicine_type" translatable="false">💊</string> <string name="event_medicine_type" translatable="false">💊</string>
@@ -20,36 +19,16 @@
<string name="event_puke_type" translatable="false">🤮</string> <string name="event_puke_type" translatable="false">🤮</string>
<string name="event_bath_type" translatable="false">🛁</string> <string name="event_bath_type" translatable="false">🛁</string>
<string name="event_sleep_type" translatable="false">💤</string> <string name="event_sleep_type" translatable="false">💤</string>
<string name="event_unknown_type" translatable="false"></string> <string name="event_unknown_type" translatable="false">\?</string>
<!-- dropdown menu item --> <string name="event_bottle_desc">Baby bottle</string>
<string name="event_type_item_bottle">🍼 Bottle</string>
<string name="event_type_item_food">🥣 Food</string>
<string name="event_type_item_weight">⚖️ Weight</string>
<string name="event_type_item_breastfeeding_left">🤱⬅️ Nursing</string>
<string name="event_type_item_breastfeeding_both">🤱↔️ Nursing</string>
<string name="event_type_item_breastfeeding_right">🤱➡️️ Nursing</string>
<string name="event_type_item_diaperchange_poo">🚼💩 Diaper</string>
<string name="event_type_item_diaperchange_pee">🚼💧 Diaper</string>
<string name="event_type_item_medicine">💊 Medicine</string>
<string name="event_type_item_enema">🪠 Enema</string>
<string name="event_type_item_note">📝 Note</string>
<string name="event_type_item_temperature">🌡️ Temperature</string>
<string name="event_type_item_colic">💨 Colic</string>
<string name="event_type_item_puke">🤮 Puke</string>
<string name="event_type_item_sleep">💤 Sleep</string>
<string name="event_type_item_bath">🛁 Bath</string>
<string name="event_type_item_unknown">❓ Unknown</string>
<!-- dialog titles -->
<string name="event_bottle_desc">Milk Bottle</string>
<string name="event_food_desc">Food</string> <string name="event_food_desc">Food</string>
<string name="event_weight_desc">Weight</string> <string name="event_weight_desc">Weight</string>
<string name="event_breastfeeding_left_desc">Nursing (left)</string> <string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
<string name="event_breastfeeding_both_desc">Nursing (both)</string> <string name="event_breastfeeding_both_desc">Breastfeeding</string>
<string name="event_breastfeeding_right_desc">Nursing (right)</string> <string name="event_breastfeeding_right_desc">Breastfeeding (right)</string>
<string name="event_diaperchange_poo_desc">Diaper Change (poo)</string> <string name="event_diaperchange_poo_desc">Diaper chg (poo)</string>
<string name="event_diaperchange_pee_desc">Diaper Change (pee)</string> <string name="event_diaperchange_pee_desc">Diaper chg (pee)</string>
<string name="event_medicine_desc">Medicine</string> <string name="event_medicine_desc">Medicine</string>
<string name="event_enema_desc">Enema</string> <string name="event_enema_desc">Enema</string>
<string name="event_note_desc">Note</string> <string name="event_note_desc">Note</string>
@@ -58,7 +37,17 @@
<string name="event_puke_desc">Puke</string> <string name="event_puke_desc">Puke</string>
<string name="event_bath_desc">Bath</string> <string name="event_bath_desc">Bath</string>
<string name="event_sleep_desc">Sleep</string> <string name="event_sleep_desc">Sleep</string>
<string name="event_unknown_desc">Unknown</string> <string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Weight</string>
<string name="overflow_event_medicine">💊 Medicine</string>
<string name="overflow_event_enema">🪠 Enema</string>
<string name="overflow_event_note">📝 Note</string>
<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_event_added">Event logged</string>
<string name="toast_logbook_saved">Logbook saved</string> <string name="toast_logbook_saved">Logbook saved</string>
@@ -90,8 +79,6 @@
<string name="statistics_title">Statistics</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> <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>
@@ -110,6 +97,8 @@
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string> <string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string> <string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string> <string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
<string name="settings_no_breastfeeding">No Breastfeeding</string>
<string name="settings_no_breastfeeding_desc">Hide the Breastfeeding buttons for when they are not needed.</string>
<string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string> <string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string>
<string name="settings_generic_error">Error: </string> <string name="settings_generic_error">Error: </string>
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string> <string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>

View File

@@ -4,11 +4,6 @@
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar"> <style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
<item name="colorAccent">@color/accent</item> <item name="colorAccent">@color/accent</item>
<item name="android:textColor">@color/textColor</item> <item name="android:textColor">@color/textColor</item>
<!-- make the screen not overlap with the system bars -->
<item name="android:fitsSystemWindows">true</item>
<item name="android:windowTranslucentStatus">true</item>
<item name="android:windowTranslucentNavigation">true</item>
</style> </style>
<style name="OverflowMenuText"> <style name="OverflowMenuText">

View File

@@ -1,5 +1,5 @@
[versions] [versions]
agp = "8.13.0" agp = "8.12.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"
@@ -13,7 +13,6 @@ mpandroidchart = "v4.2.2"
mpandroidchartVersion = "v3.1.0" mpandroidchartVersion = "v3.1.0"
recyclerview = "1.3.2" recyclerview = "1.3.2"
material = "1.12.0" material = "1.12.0"
sardineAndroid = "v0.9"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -35,7 +34,6 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
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 = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" }
mpandroidchart-vv310 = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchartVersion" } mpandroidchart-vv310 = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchartVersion" }
sardine-android = { module = "com.github.thegrizzlylabs:sardine-android", version.ref = "sardineAndroid" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }