14 Commits

Author SHA1 Message Date
4f0416e1c1 NumericUtils: remove possible trailing whitespace 2025-12-14 19:42:08 +01:00
8c2fcaaef5 MainActivity: do not switch logbook on reload 2025-12-14 19:42:08 +01:00
01907d7c4e LunaEvent: reorganize event text getters
Use method names that better reflect
the use of the returned text.
2025-12-14 19:42:08 +01:00
3a3dc2faf4 Add dynamic header setting
This makes the 'no breastfeeding' setting irrelevant.
2025-12-14 19:42:08 +01:00
2518118759 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.
2025-12-14 19:42:08 +01:00
d8b67531db MainActivity: increase bottle volume to 340ml
This is the maximum amount found in sold bottles.
2025-12-14 19:42:08 +01:00
0a424e8807 improve statistics 2025-12-14 19:42:08 +01:00
8adacd3bfe gradle: use uniform implementation directive for sardine-android 2025-12-14 19:42:08 +01:00
508c14ff6e gradle: avoid inclusion of apk signing blobs
See https://android.izzysoft.de/articles/named/iod-scan-apkchecks?lang=en#blobs
2025-12-14 19:42:08 +01:00
7d67a2e33b gradle: set compileSDK/targetSdk to 36 2025-12-14 19:42:08 +01:00
a0dc023941 add statistics for bottle and sleep events 2025-12-14 19:42:08 +01:00
6759956461 MainActivity: show save button if any values has changed 2025-12-14 19:42:08 +01:00
c521a3373f MainActivity: use unique templates for notes 2025-12-14 19:42:08 +01:00
1ad5f31b32 LunaEvent: add sleep event 2025-12-14 19:42:03 +01:00
17 changed files with 917 additions and 1097 deletions

View File

@@ -6,12 +6,12 @@ plugins {
android {
namespace = "it.danieleverducci.lunatracker"
compileSdk = 34
compileSdk = 36
defaultConfig {
applicationId = "it.danieleverducci.lunatracker"
minSdk = 21
targetSdk = 34
targetSdk = 36
versionCode = 7
versionName = "0.9"
@@ -31,6 +31,12 @@ android {
sourceCompatibility = 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 {
jvmTarget = "11"
}
@@ -40,7 +46,6 @@ android {
}
dependencies {
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
@@ -51,7 +56,7 @@ dependencies {
implementation(libs.androidx.material3)
implementation(libs.androidx.appcompat)
implementation(libs.androidx.recyclerview)
implementation("com.github.thegrizzlylabs:sardine-android:v0.9")
implementation(libs.sardine.android)
implementation(libs.material)
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
@@ -60,7 +65,5 @@ dependencies {
androidTestImplementation(libs.androidx.ui.test.junit4)
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
implementation("com.github.PhilJay:MPAndroidChart:v3.1.0")
// implementation(libs.mpandroidchart)
//implementation project(':MPChartLib')
implementation(libs.mpandroidchart.vv310)
}

View File

@@ -15,6 +15,7 @@ import android.widget.AdapterView
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.EditText
import android.widget.LinearLayout
import android.widget.NumberPicker
import android.widget.PopupWindow
import android.widget.Spinner
@@ -51,6 +52,9 @@ class MainActivity : AppCompatActivity() {
const val TAG = "MainActivity"
const val UPDATE_EVERY_SECS: Long = 30
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
// list of all events
var allEvents = arrayListOf<LunaEvent>()
var currentPopupItems = listOf<LunaEvent.Type>()
}
var logbook: Logbook? = null
@@ -82,31 +86,13 @@ class MainActivity : AppCompatActivity() {
recyclerView = findViewById(R.id.list_events)
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
populateHeaderMenu()
// Set listeners
findViewById<View>(R.id.logbooks_add_button).setOnClickListener {
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)
moreButton.setOnClickListener {
showOverflowPopupWindow(moreButton)
@@ -122,9 +108,13 @@ class MainActivity : AppCompatActivity() {
loadLogbookList()
}
findViewById<View>(R.id.button_sync).setOnClickListener {
if (logbook != null) {
loadLogbook(logbook!!.name)
} else {
loadLogbookList()
}
}
}
private fun setListAdapter(items: ArrayList<LunaEvent>) {
val adapter = LunaEventRecyclerAdapter(this, items)
@@ -146,7 +136,84 @@ class MainActivity : AppCompatActivity() {
if (logbook == null)
Log.w(TAG, "showLogbook(): logbook is null!")
setListAdapter(logbook?.logs ?: arrayListOf())
allEvents = logbook?.logs ?: arrayListOf()
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() {
@@ -169,12 +236,6 @@ class MainActivity : AppCompatActivity() {
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
recyclerView.adapter?.notifyDataSetChanged()
@@ -193,10 +254,6 @@ class MainActivity : AppCompatActivity() {
super.onStop()
}
fun getAllEvents(): ArrayList<LunaEvent> {
return logbook?.logs ?: arrayListOf()
}
fun addBabyBottleEvent(event: LunaEvent) {
setToPreviousQuantity(event)
askBabyBottleContent(event, true) {
@@ -207,14 +264,14 @@ class MainActivity : AppCompatActivity() {
fun askBabyBottleContent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_bottle, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.dialog_number_picker)
numberPicker.minValue = 1 // "10"
numberPicker.maxValue = 25 // "250
numberPicker.displayedValues = ((10..250 step 10).map { it.toString() }.toTypedArray())
numberPicker.maxValue = 34 // "340
numberPicker.displayedValues = ((10..340 step 10).map { it.toString() }.toTypedArray())
numberPicker.wrapSelectorWheel = false
numberPicker.value = event.quantity / 10
@@ -249,7 +306,7 @@ class MainActivity : AppCompatActivity() {
// Show number picker dialog
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_weight, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
@@ -293,12 +350,12 @@ class MainActivity : AppCompatActivity() {
// Show number picker dialog
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_temperature, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
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.valueTo = range.second.toFloat()
tempSlider.value = if (event.quantity == 0) {
@@ -364,7 +421,7 @@ class MainActivity : AppCompatActivity() {
}
fun saveEvent(event: LunaEvent) {
if (!getAllEvents().contains(event)) {
if (!allEvents.contains(event)) {
// new event
logEvent(event)
}
@@ -381,7 +438,7 @@ class MainActivity : AppCompatActivity() {
fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
@@ -397,8 +454,8 @@ class MainActivity : AppCompatActivity() {
var duration = event.quantity
fun isValidTime(timeSeconds: Long, durationSeconds: Int): Boolean {
val now = Calendar.getInstance().time.time / 1000
return (timeSeconds + durationSeconds) <= now && durationSeconds < (12 * 60 * 60)
val now = System.currentTimeMillis() / 1000
return (timeSeconds + durationSeconds) <= now && durationSeconds < (24 * 60 * 60)
}
val onDateChange = { time: Long ->
@@ -431,7 +488,7 @@ class MainActivity : AppCompatActivity() {
durationPlus5Button.setOnClickListener { adjust(5) }
durationNowButton.setOnClickListener {
val now = Calendar.getInstance().time.time / 1000
val now = System.currentTimeMillis() / 1000
val start = pickedDateTime.time.time / 1000
if (now > start) {
duration = (now - start).toInt()
@@ -442,7 +499,6 @@ class MainActivity : AppCompatActivity() {
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val time = pickedDateTime.time.time / 1000
if (isValidTime(time, duration)) {
event.time = time
event.quantity = duration
@@ -468,7 +524,7 @@ class MainActivity : AppCompatActivity() {
fun askAmountValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_amount, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
@@ -510,7 +566,7 @@ class MainActivity : AppCompatActivity() {
fun askDateValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_plain, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
@@ -539,11 +595,11 @@ class MainActivity : AppCompatActivity() {
}
fun askNotes(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) {
val useQuantity = (event.type != LunaEvent.TYPE_NOTE && event.type != LunaEvent.TYPE_CUSTOM)
val useQuantity = (event.type != LunaEvent.Type.NOTE)
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_edit_notes, null)
d.setTitle(event.getTypeDescription(this))
d.setTitle(event.getDialogTitle(this))
d.setMessage(event.getDialogMessage(this))
d.setView(dialogView)
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
@@ -558,7 +614,7 @@ class MainActivity : AppCompatActivity() {
val nextTextView = dialogView.findViewById<TextView>(R.id.notes_template_next)
val prevTextView = dialogView.findViewById<TextView>(R.id.notes_template_prev)
val templates = getAllEvents().filter { it.type == event.type }.distinctBy { it.notes.trim() }.sortedBy { it.time }
val templates = allEvents.filter { it.type == event.type }.distinctBy { it.notes }.sortedBy { it.time }
fun updateContent(current: LunaEvent) {
val prevEvent = getPreviousSameEvent(current, templates)
@@ -613,7 +669,7 @@ class MainActivity : AppCompatActivity() {
updateContent(event)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val notes = notesET.text.toString()
val notes = notesET.text.toString().trim()
if (useQuantity) {
val quantity = qtyET.text.toString().toIntOrNull()
@@ -664,7 +720,7 @@ class MainActivity : AppCompatActivity() {
}
fun setToPreviousQuantity(event: LunaEvent) {
val prev = getPreviousSameEvent(event, getAllEvents())
val prev = getPreviousSameEvent(event, allEvents)
if (prev != null) {
event.quantity = prev.quantity
}
@@ -721,8 +777,8 @@ class MainActivity : AppCompatActivity() {
val quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
val notesTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes)
emojiTextView.text = event.getTypeEmoji(this)
descriptionTextView.text = event.getTypeDescription(this)
emojiTextView.text = event.getHeaderEmoji(this)
descriptionTextView.text = event.getDialogTitle(this)
d.setView(dialogView)
@@ -761,7 +817,7 @@ class MainActivity : AppCompatActivity() {
val updateValues = {
quantityTextView.text = NumericUtils(this).formatEventQuantity(event)
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.visibility = View.VISIBLE
} else {
@@ -783,22 +839,28 @@ class MainActivity : AppCompatActivity() {
quantityTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.TYPE_WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.TYPE_TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
LunaEvent.TYPE_SLEEP -> askSleepValue(event, updateValues)
LunaEvent.Type.BABY_BOTTLE -> askBabyBottleContent(event, false, updateValues)
LunaEvent.Type.WEIGHT -> askWeightValue(event, false, updateValues)
LunaEvent.Type.DIAPERCHANGE_POO,
LunaEvent.Type.DIAPERCHANGE_PEE,
LunaEvent.Type.PUKE -> askAmountValue(event, false, updateValues)
LunaEvent.Type.TEMPERATURE -> askTemperatureValue(event, false, updateValues)
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.SLEEP -> askSleepValue(event, updateValues)
else -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
}
}
notesTextView.setOnClickListener {
when (event.type) {
LunaEvent.TYPE_FOOD,
LunaEvent.TYPE_MEDICINE,
LunaEvent.TYPE_NOTE -> askNotes(event, false, updateValues)
LunaEvent.Type.FOOD,
LunaEvent.Type.MEDICINE,
LunaEvent.Type.NOTE -> askNotes(event, false, updateValues)
else -> {
Log.w(TAG, "Unexpected type: ${event.type}")
}
}
}
@@ -809,13 +871,11 @@ class MainActivity : AppCompatActivity() {
signatureTextEdit.visibility = View.VISIBLE
}
val allEvents = getAllEvents()
// create link to prevent event of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
val previousEvent = getPreviousSameEvent(event, allEvents)
if (previousEvent != null) {
val emoji = previousEvent.getTypeEmoji(applicationContext)
val emoji = previousEvent.getHeaderEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
previousTextView.text = String.format("⬅️ %s %s", emoji, time)
previousTextView.setOnClickListener {
@@ -830,7 +890,7 @@ class MainActivity : AppCompatActivity() {
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next)
val nextEvent = getNextSameEvent(event, allEvents)
if (nextEvent != null) {
val emoji = nextEvent.getTypeEmoji(applicationContext)
val emoji = nextEvent.getHeaderEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
nextTextView.text = String.format("%s %s ➡️", time, emoji)
nextTextView.setOnClickListener {
@@ -1001,7 +1061,7 @@ class MainActivity : AppCompatActivity() {
if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) {
for (e in logbook?.logs ?: listOf()) {
val em = e.getTypeEmoji(this@MainActivity)
val em = e.getHeaderEmoji(this@MainActivity)
if (em == getString(R.string.event_unknown_type)) {
Log.e(TAG, "UNKNOWN: ${e.type}")
}
@@ -1186,6 +1246,29 @@ 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) {
if (showingOverflowPopupWindow)
return
@@ -1194,42 +1277,8 @@ class MainActivity : AppCompatActivity() {
isOutsideTouchable = true
val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.more_events_popup, null)
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
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()
}
// Add statistics (hard coded)
contentView.findViewById<View>(R.id.button_statistics).setOnClickListener {
if (logbook != null && !pauseLogbookUpdate) {
val i = Intent(applicationContext, StatisticsActivity::class.java)
@@ -1240,6 +1289,20 @@ class MainActivity : AppCompatActivity() {
}
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 ->
popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({

View File

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

View File

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

View File

@@ -12,25 +12,24 @@ import java.util.Date
* release, it is simply ignored by previous ones).
*/
class LunaEvent: Comparable<LunaEvent> {
companion object {
const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
const val TYPE_WEIGHT = "WEIGHT"
const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
const val TYPE_MEDICINE = "MEDICINE"
const val TYPE_ENEMA = "ENEMA"
const val TYPE_NOTE = "NOTE"
const val TYPE_CUSTOM = "CUSTOM"
const val TYPE_COLIC = "COLIC"
const val TYPE_TEMPERATURE = "TEMPERATURE"
const val TYPE_FOOD = "FOOD"
const val TYPE_PUKE = "PUKE"
const val TYPE_BATH = "BATH"
const val TYPE_SLEEP = "SLEEP"
enum class Type {
BABY_BOTTLE,
FOOD,
BREASTFEEDING_LEFT_NIPPLE,
BREASTFEEDING_BOTH_NIPPLE,
BREASTFEEDING_RIGHT_NIPPLE,
DIAPERCHANGE_POO,
DIAPERCHANGE_PEE,
SLEEP,
WEIGHT,
MEDICINE,
ENEMA,
NOTE,
COLIC,
TEMPERATURE,
PUKE,
BATH,
UNKNOWN
}
private val jo: JSONObject
@@ -40,10 +39,16 @@ class LunaEvent: Comparable<LunaEvent> {
set(value) {
jo.put("time", value)
}
var type: String
get(): String = jo.getString("type")
var type: Type
get(): Type {
return try {
Type.valueOf(jo.getString("type"))
} catch (_: Exception) {
Type.UNKNOWN
}
}
set(value) {
jo.put("type", value)
jo.put("type", value.name)
}
var quantity: Int
get() = jo.optInt("quantity")
@@ -81,81 +86,33 @@ class LunaEvent: Comparable<LunaEvent> {
this.signature = event.signature
}
constructor(type: String) {
constructor(type: Type) {
this.jo = JSONObject()
this.time = System.currentTimeMillis() / 1000
this.type = type
}
constructor(type: String, quantity: Int) {
constructor(type: Type, quantity: Int) {
this.jo = JSONObject()
this.time = System.currentTimeMillis() / 1000
this.type = type
this.quantity = quantity
}
fun getTypeEmoji(context: Context): 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
else -> R.string.event_unknown_type
}
)
fun getHeaderEmoji(context: Context): String {
return getHeaderEmoji(context, type)
}
fun getTypeDescription(context: Context): 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
else -> R.string.event_unknown_desc
}
)
fun getDialogTitle(context: Context): String {
return getDialogTitle(context, type)
}
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 getRowItemTitle(context: Context): String {
return getPopupItemTitle(context, type).split(" ", limit = 2).last() // remove emoji
}
)
fun getDialogMessage(context: Context): String {
return getDialogMessage(context, type)
}
fun toJson(): JSONObject {
@@ -169,4 +126,95 @@ class LunaEvent: Comparable<LunaEvent> {
override fun compareTo(other: LunaEvent): Int {
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_USER = "webdav_user"
const val SHARED_PREFS_DAV_PASS = "webdav_password"
const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
const val SHARED_PREFS_DYNAMIC_MENU = "dynamic_menu"
const val SHARED_PREFS_SIGNATURE = "signature"
}
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
@@ -31,12 +31,12 @@ class LocalSettingsRepository(val context: Context) {
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
}
fun saveNoBreastfeeding(content: Boolean) {
sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
fun saveDynamicMenu(content: Boolean) {
sharedPreferences.edit { putBoolean(SHARED_PREFS_DYNAMIC_MENU, content) }
}
fun loadNoBreastfeeding(): Boolean {
return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false)
fun loadDynamicMenu(): Boolean {
return sharedPreferences.getBoolean(SHARED_PREFS_DYNAMIC_MENU, false)
}
fun saveDataRepository(repo: DATA_REPO) {

View File

@@ -66,51 +66,51 @@ class NumericUtils (val context: Context) {
return formatEventQuantity(event.type, event.quantity)
}
fun formatEventQuantity(type: String, quantity: Int): String {
fun formatEventQuantity(type: LunaEvent.Type, quantity: Int): String {
val formatted = StringBuilder()
if (quantity > 0) {
formatted.append(when (type) {
LunaEvent.TYPE_TEMPERATURE ->
LunaEvent.Type.TEMPERATURE ->
(quantity / 10.0f).toString()
LunaEvent.TYPE_DIAPERCHANGE_POO,
LunaEvent.TYPE_DIAPERCHANGE_PEE,
LunaEvent.TYPE_PUKE -> {
LunaEvent.Type.DIAPERCHANGE_POO,
LunaEvent.Type.DIAPERCHANGE_PEE,
LunaEvent.Type.PUKE -> {
val array = context.resources.getStringArray(R.array.AmountLabels)
return array.getOrElse(quantity) {
Log.e("NumericUtils", "Invalid index $quantity")
return ""
}
}
LunaEvent.TYPE_SLEEP -> formatTimeDuration(context, quantity.toLong())
LunaEvent.Type.SLEEP -> formatTimeDuration(context, quantity.toLong())
else -> quantity
})
formatted.append(" ")
formatted.append(
when (type) {
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
LunaEvent.Type.BABY_BOTTLE -> measurement_unit_liquid_base
LunaEvent.Type.WEIGHT -> measurement_unit_weight_base
LunaEvent.Type.MEDICINE -> measurement_unit_weight_tiny
LunaEvent.Type.TEMPERATURE -> measurement_unit_temperature_base
else -> ""
}
)
} else {
formatted.append(when (type) {
LunaEvent.TYPE_SLEEP -> "💤" // baby is sleeping
LunaEvent.Type.SLEEP -> "💤" // baby is sleeping
else -> ""
})
}
return formatted.toString()
return formatted.toString().trim()
}
/**
* Returns a valid quantity range for the event type.
* @return min, max, normal
*/
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
fun getValidEventQuantityRange(lunaEventType: LunaEvent.Type): Triple<Int, Int, Int>? {
return when (lunaEventType) {
LunaEvent.TYPE_TEMPERATURE -> {
LunaEvent.Type.TEMPERATURE -> {
if (isMetricSystem())
Triple(
context.resources.getInteger(R.integer.human_body_temp_min_metric),

View File

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

View File

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

View File

@@ -29,25 +29,27 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:layout_marginTop="10dp"
android:orientation="horizontal">
<Spinner
android:id="@+id/type_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android: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>

View File

@@ -6,6 +6,7 @@
android:background="@color/transparent">
<LinearLayout
android:id="@+id/layout_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
@@ -14,102 +15,12 @@
android:id="@+id/button_statistics"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="📊 Statistics"/>
<TextView
android:id="@+id/button_medicine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
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"/>
<!-- Other buttons are inserted dynamically -->
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,11 @@
<?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,15 +8,23 @@
</string-array>
<string-array name="StatisticsTypeLabels">
<item>Bottle</item>
<item>Sleep</item>
<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</item>
<item>SLEEP</item>
<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>
@@ -26,7 +34,7 @@
<item>EVENT</item>
<item>AMOUNT</item>
</string-array>
-->
<string-array name="StatisticsTimeLabels">
<item>Day</item>
<item>Week</item>
@@ -39,11 +47,4 @@
<item>MONTH</item>
</string-array>
<!--
<string-array name="speakerphoneModeValues">
<item>auto</item>
<item>on</item>
<item>off</item>
</string-array>
-->
</resources>

View File

@@ -3,12 +3,13 @@
<string name="title">🌜 LunaTracker 🌛</string>
<string name="logbook">Logged events</string>
<!-- menu header items -->
<string name="event_bottle_type" translatable="false">🍼</string>
<string name="event_food_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_both_type" translatable="false">🤱 </string>
<string name="event_breastfeeding_right_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_right_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_medicine_type" translatable="false">💊</string>
@@ -19,16 +20,36 @@
<string name="event_puke_type" translatable="false">🤮</string>
<string name="event_bath_type" translatable="false">🛁</string>
<string name="event_sleep_type" translatable="false">💤</string>
<string name="event_unknown_type" translatable="false">\?</string>
<string name="event_unknown_type" translatable="false"></string>
<string name="event_bottle_desc">Baby bottle</string>
<!-- dropdown menu item -->
<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_weight_desc">Weight</string>
<string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
<string name="event_breastfeeding_both_desc">Breastfeeding</string>
<string name="event_breastfeeding_right_desc">Breastfeeding (right)</string>
<string name="event_diaperchange_poo_desc">Diaper chg (poo)</string>
<string name="event_diaperchange_pee_desc">Diaper chg (pee)</string>
<string name="event_breastfeeding_left_desc">Nursing (left)</string>
<string name="event_breastfeeding_both_desc">Nursing (both)</string>
<string name="event_breastfeeding_right_desc">Nursing (right)</string>
<string name="event_diaperchange_poo_desc">Diaper Change (poo)</string>
<string name="event_diaperchange_pee_desc">Diaper Change (pee)</string>
<string name="event_medicine_desc">Medicine</string>
<string name="event_enema_desc">Enema</string>
<string name="event_note_desc">Note</string>
@@ -37,17 +58,7 @@
<string name="event_puke_desc">Puke</string>
<string name="event_bath_desc">Bath</string>
<string name="event_sleep_desc">Sleep</string>
<string name="event_unknown_desc"></string>
<string name="overflow_event_weight">⚖️ Weight</string>
<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="event_unknown_desc">Unknown</string>
<string name="toast_event_added">Event logged</string>
<string name="toast_logbook_saved">Logbook saved</string>
@@ -79,6 +90,8 @@
<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_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>
@@ -97,8 +110,6 @@
<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_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_generic_error">Error: </string>
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>

View File

@@ -4,6 +4,11 @@
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
<item name="colorAccent">@color/accent</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 name="OverflowMenuText">

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.12.0"
agp = "8.13.0"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"
@@ -13,6 +13,7 @@ mpandroidchart = "v4.2.2"
mpandroidchartVersion = "v3.1.0"
recyclerview = "1.3.2"
material = "1.12.0"
sardineAndroid = "v0.9"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -34,6 +35,7 @@ androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version
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]
android-application = { id = "com.android.application", version.ref = "agp" }