19 Commits
v0.2 ... v0.4

Author SHA1 Message Date
b1e26f8a96 Added Temperature and Gaseous colic events, removed custom event stub 2025-01-09 08:27:36 +01:00
48017f70f6 Fix typo in fastlane and readme 2025-01-08 07:49:10 +01:00
524a121149 Dismiss overflow popup on click outside, avoid reopening on overflow button click 2025-01-08 07:46:49 +01:00
e1388f9bad Consistency check 2025-01-08 07:35:02 +01:00
90761afef2 Changed fastlane description 2024-12-16 08:27:01 +01:00
b02a6c9aad Check on weight and red color on weight loss, fixed layout on small screen or screens with high font size 2024-12-11 08:43:14 +01:00
0d331d90ff Settings style 2024-12-03 08:48:49 +01:00
5d17ebf46b Release 0.3 2024-11-27 08:16:17 +01:00
3cbc4139ae Lowered minimum SDK 2024-11-27 08:14:48 +01:00
f31c68193a Graphics edits 2024-11-27 07:45:43 +01:00
de5b1d4ead Better number formatting 2024-11-24 10:39:14 +01:00
de75ed584b WIP detail view 2024-11-24 10:19:47 +01:00
a4b43ffb10 Alternate colorized rows in list 2024-11-24 09:46:39 +01:00
37b68450da List spacing 2024-11-24 09:37:17 +01:00
841da9e28e Added type Note 2024-11-24 09:32:05 +01:00
876c68ce81 Measurement units 2024-11-24 09:04:19 +01:00
35a3b42a32 Avoid frequent reloads 2024-11-24 08:31:30 +01:00
d5cb30cdd7 Added Medicine and Enema types, overflow menu 2024-11-24 08:26:39 +01:00
57f997e6f1 Screenshot in readme 2024-11-23 08:30:49 +01:00
26 changed files with 800 additions and 106 deletions

View File

@ -3,8 +3,10 @@
LunaTracker is a newborn baby tracking app. LunaTracker is a newborn baby tracking app.
Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying. Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying.
You have to react fast, event if it's 4 AM and you have no idea why is crying. You have to react fast, even if it's 4 AM and you have no idea why is crying.
This app is meant to log all the relevant events (diaper change, breastfeeding, baby bottle feeding...), so you can always remember when has been done last time. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attentions, the dad can step in leaving the mom sleeping peacefully. This app is meant to log all the relevant events (diaper change, breastfeeding, baby bottle feeding...), so you can always remember when has been done last time. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attentions, the dad can step in leaving the mom sleeping peacefully.
Dedicated to my daughter Luna. Dedicated to my daughter Luna.
![Screenshot](fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)

View File

@ -10,10 +10,10 @@ android {
defaultConfig { defaultConfig {
applicationId = "it.danieleverducci.lunatracker" applicationId = "it.danieleverducci.lunatracker"
minSdk = 29 minSdk = 21
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 2
versionName = "0.2" versionName = "0.3"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -4,9 +4,12 @@ import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.Handler
import android.util.Log import android.util.Log
import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.EditText
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.PopupWindow
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
@ -14,6 +17,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.progressindicator.LinearProgressIndicator
import com.google.android.material.slider.Slider
import com.thegrizzlylabs.sardineandroid.impl.SardineException import com.thegrizzlylabs.sardineandroid.impl.SardineException
import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter
import it.danieleverducci.lunatracker.entities.Logbook import it.danieleverducci.lunatracker.entities.Logbook
@ -27,10 +31,15 @@ import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
import kotlinx.coroutines.Runnable import kotlinx.coroutines.Runnable
import okio.IOException import okio.IOException
import org.json.JSONException import org.json.JSONException
import utils.NumericUtils
import java.text.DateFormat
import java.util.Date
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
companion object { companion object {
val TAG = "MainActivity" val TAG = "MainActivity"
val UPDATE_EVERY_SECS: Long = 30
val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
} }
lateinit var logbook: Logbook lateinit var logbook: Logbook
@ -45,12 +54,18 @@ class MainActivity : AppCompatActivity() {
handler.postDelayed(updateListRunnable, 1000*60) handler.postDelayed(updateListRunnable, 1000*60)
} }
var logbookRepo: LogbookRepository? = null var logbookRepo: LogbookRepository? = null
var showingOverflowPopupWindow = false
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
handler = Handler(mainLooper) handler = Handler(mainLooper)
adapter = LunaEventRecyclerAdapter(this) adapter = LunaEventRecyclerAdapter(this)
adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener{
override fun onItemClick(event: LunaEvent) {
showEventDetailDialog(event)
}
}
// Show view // Show view
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -89,6 +104,10 @@ class MainActivity : AppCompatActivity() {
LunaEvent.TYPE_DIAPERCHANGE_PEE LunaEvent.TYPE_DIAPERCHANGE_PEE
) )
) } ) }
val moreButton = findViewById<View>(R.id.button_more)
moreButton.setOnClickListener {
showOverflowPopupWindow(moreButton)
}
findViewById<View>(R.id.button_no_connection_settings).setOnClickListener({ findViewById<View>(R.id.button_no_connection_settings).setOnClickListener({
showSettings() showSettings()
}) })
@ -138,7 +157,6 @@ class MainActivity : AppCompatActivity() {
// Reload data // Reload data
loadLogbook() loadLogbook()
handler.postDelayed(updateListRunnable, 1000*30)
} }
override fun onStop() { override fun onStop() {
@ -177,7 +195,7 @@ class MainActivity : AppCompatActivity() {
d.setTitle(R.string.log_weight_dialog_title) d.setTitle(R.string.log_weight_dialog_title)
d.setMessage(R.string.log_weight_dialog_description) d.setMessage(R.string.log_weight_dialog_description)
d.setView(dialogView) d.setView(dialogView)
val weightET = dialogView.findViewById<TextView>(R.id.dialog_number_edittext) val weightET = dialogView.findViewById<EditText>(R.id.dialog_number_edittext)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val weight = weightET.text.toString().toIntOrNull() val weight = weightET.text.toString().toIntOrNull()
if (weight != null) if (weight != null)
@ -190,6 +208,64 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun askTemperatureValue() {
// Show number picker dialog
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.temperature_dialog, null)
d.setTitle(R.string.log_temperature_dialog_title)
d.setMessage(R.string.log_temperature_dialog_description)
d.setView(dialogView)
val tempSlider = dialogView.findViewById<Slider>(R.id.dialog_temperature_value)
val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.TYPE_TEMPERATURE)!!
tempSlider.valueFrom = range.first.toFloat()
tempSlider.valueTo = range.second.toFloat()
tempSlider.value = range.third.toFloat()
val tempTextView = dialogView.findViewById<TextView>(R.id.dialog_temperature_display)
tempTextView.text = range.third.toString()
tempSlider.addOnChangeListener({s, v, b -> tempTextView.text = v.toString()})
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val temperature = (tempSlider.value * 10).toInt() // In tenth of a grade
logEvent(LunaEvent(LunaEvent.TYPE_TEMPERATURE, temperature))
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
val alertDialog = d.create()
alertDialog.show()
}
fun askNotes(lunaEvent: LunaEvent) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_notes, null)
d.setTitle(lunaEvent.getTypeDescription(this))
d.setMessage(
when (lunaEvent.type){
LunaEvent.TYPE_MEDICINE -> R.string.log_medicine_dialog_description
else -> R.string.log_notes_dialog_description
}
)
d.setView(dialogView)
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
if (lunaEvent.type == LunaEvent.TYPE_NOTE || lunaEvent.type == LunaEvent.TYPE_CUSTOM)
qtyET.visibility = View.GONE
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val qtyStr = qtyET.text.toString()
if (qtyStr.isNotEmpty()) {
val qty = qtyStr.toIntOrNull()
if (qty == null) {
Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show()
return@setPositiveButton
}
lunaEvent.quantity = qty
}
val notes = notesET.text.toString()
lunaEvent.notes = notes
logEvent(lunaEvent)
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
val alertDialog = d.create()
alertDialog.show()
}
fun askToTrimLogbook() { fun askToTrimLogbook() {
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
d.setTitle(R.string.trim_logbook_dialog_title) d.setTitle(R.string.trim_logbook_dialog_title)
@ -210,10 +286,32 @@ class MainActivity : AppCompatActivity() {
alertDialog.show() alertDialog.show()
} }
fun showEventDetailDialog(event: LunaEvent) {
val dateFormat = DateFormat.getDateTimeInstance();
val d = AlertDialog.Builder(this)
d.setTitle(R.string.dialog_event_detail_title)
val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji).setText(event.getTypeEmoji(this))
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description).setText(event.getTypeDescription(this))
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date).setText(dateFormat.format(Date(event.time * 1000)))
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity).setText(
NumericUtils(this).formatEventQuantity(event)
)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).setText(event.notes)
d.setView(dialogView)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> dialogInterface.dismiss() }
val alertDialog = d.create()
alertDialog.show()
}
fun loadLogbook() { fun loadLogbook() {
if (savingEvent) if (savingEvent)
return return
// Reset time counter
handler.removeCallbacks(updateListRunnable)
handler.postDelayed(updateListRunnable, UPDATE_EVERY_SECS*1000)
// Load data // Load data
setLoading(true) setLoading(true)
logbookRepo?.loadLogbook(this, object: LogbookLoadedListener{ logbookRepo?.loadLogbook(this, object: LogbookLoadedListener{
@ -223,6 +321,15 @@ class MainActivity : AppCompatActivity() {
findViewById<View>(R.id.no_connection_screen).visibility = View.GONE findViewById<View>(R.id.no_connection_screen).visibility = View.GONE
logbook = lb logbook = lb
showLogbook() showLogbook()
if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) {
for (e in logbook.logs) {
val em = e.getTypeEmoji(this@MainActivity)
if (em == getString(R.string.event_unknown_type)) {
Log.e(TAG, "UNKNOWN: ${e.type}")
}
}
}
}) })
} }
@ -384,4 +491,44 @@ class MainActivity : AppCompatActivity() {
} }
} }
private fun showOverflowPopupWindow(anchor: View) {
if (showingOverflowPopupWindow)
return
PopupWindow(anchor.context).apply {
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({
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE))
dismiss()
})
contentView.findViewById<View>(R.id.button_enema).setOnClickListener({
logEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
dismiss()
})
contentView.findViewById<View>(R.id.button_note).setOnClickListener({
askNotes(LunaEvent(LunaEvent.TYPE_NOTE))
dismiss()
})
contentView.findViewById<View>(R.id.button_temperature).setOnClickListener({
askTemperatureValue()
dismiss()
})
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({
logEvent(
LunaEvent(LunaEvent.TYPE_COLIC)
)
dismiss()
})
}.also { popupWindow ->
popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({
showingOverflowPopupWindow = false
}, 500)
})
popupWindow.showAsDropDown(anchor)
showingOverflowPopupWindow = true
}
}
} }

View File

@ -1,30 +1,42 @@
package it.danieleverducci.lunatracker.adapters package it.danieleverducci.lunatracker.adapters
import android.content.Context import android.content.Context
import android.text.format.DateFormat
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.entities.LunaEvent
import it.danieleverducci.lunatracker.R import it.danieleverducci.lunatracker.R
import java.util.Date import utils.DateUtils
import utils.NumericUtils
class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.LunaEventVH> { class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.LunaEventVH> {
private val context: Context private val context: Context
val items = ArrayList<LunaEvent>() val items = ArrayList<LunaEvent>()
val numericUtils: NumericUtils
var onItemClickListener: OnItemClickListener? = null
val layoutRes: Int
constructor(context: Context) { constructor(context: Context) {
this.context = context this.context = context
this.numericUtils = NumericUtils(context)
val fontScale = context.resources.configuration.fontScale
val screenSize = context.resources.configuration.screenWidthDp
this.layoutRes =
if(fontScale > 1.2 || screenSize < 320 || (fontScale > 1 && screenSize < 400))
R.layout.row_luna_event_vertical
else
R.layout.row_luna_event
} }
override fun onCreateViewHolder( override fun onCreateViewHolder(
parent: ViewGroup, parent: ViewGroup,
viewType: Int viewType: Int
): LunaEventVH { ): LunaEventVH {
val view = LayoutInflater.from(context).inflate(layoutRes, parent, false)
val view = LayoutInflater.from(context).inflate(R.layout.row_luna_event, parent, false)
return LunaEventVH(view) return LunaEventVH(view)
} }
@ -33,86 +45,78 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
position: Int position: Int
) { ) {
val item = items.get(position) val item = items.get(position)
holder.type.text = context.getString( // Colors
when (item.type) { holder.root.setBackgroundResource(
LunaEvent.TYPE_BABY_BOTTLE -> R.string.event_bottle_type if (position % 2 == 0) R.color.list_background_even else R.color.list_background_odd
LunaEvent.TYPE_WEIGHT -> R.string.event_scale_type
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
LunaEvent.TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
LunaEvent.TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
else -> R.string.event_unknown_type
}
) )
holder.description.text = context.getString( holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
when (item.type) { // Contents
LunaEvent.TYPE_BABY_BOTTLE -> R.string.event_bottle_desc holder.type.text = item.getTypeEmoji(context)
LunaEvent.TYPE_WEIGHT -> R.string.event_scale_desc holder.description.text = when(item.type) {
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc LunaEvent.TYPE_MEDICINE -> item.notes
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc LunaEvent.TYPE_NOTE -> item.notes
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc LunaEvent.TYPE_CUSTOM -> item.notes
LunaEvent.TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc else -> item.getTypeDescription(context)
LunaEvent.TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc }
else -> R.string.event_unknown_desc holder.time.text = DateUtils.formatTimeAgo(context, item.time)
var quantityText = numericUtils.formatEventQuantity(item)
// if the event is weight, show difference with the last one
if (item.type == LunaEvent.TYPE_WEIGHT) {
val lastWeight = getPreviousWeightEvent(position)
if (lastWeight != null) {
val differenceInWeight = item.quantity - lastWeight.quantity
val sign = if (differenceInWeight > 0) "+" else ""
quantityText += "\n($sign$differenceInWeight)"
if (differenceInWeight < 0) {
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.danger))
}
}
}
holder.quantity.text = quantityText
// Listeners
if (onItemClickListener != null) {
holder.root.setOnClickListener({
onItemClickListener?.onItemClick(item)
})
} }
)
holder.quantity.text = if ((item.quantity ?: 0) > 0) item.quantity.toString() else ""
holder.time.text = formatTimeAgo(context, item.time)
} }
override fun getItemCount(): Int { override fun getItemCount(): Int {
return items.size return items.size
} }
/** private fun getPreviousWeightEvent(startFromPosition: Int): LunaEvent? {
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago) if (startFromPosition == items.size - 1)
*/ return null
fun formatTimeAgo(context: Context, unixTime: Long): String { for (pos in startFromPosition + 1 until items.size) {
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime val item = items.get(pos)
val minutesDiff = secondsDiff / 60 if (item.type != LunaEvent.TYPE_WEIGHT)
continue
if (minutesDiff < 1) return item
return context.getString(R.string.now)
val hoursAgo = (secondsDiff / (60 * 60)).toInt()
val minutesAgo = (minutesDiff % 60).toInt()
if (hoursAgo > 24)
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
var formattedTime = StringBuilder()
if (hoursAgo > 0) {
formattedTime.append(hoursAgo).append(" ")
if (hoursAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.hour_ago))
else
formattedTime.append(context.getString(R.string.hours_ago))
} }
if (formattedTime.isNotEmpty()) return null
formattedTime.append(", ")
if (minutesAgo > 0) {
formattedTime.append(minutesAgo).append(" ")
if (minutesAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.minute_ago))
else
formattedTime.append(context.getString(R.string.minutes_ago))
}
return formattedTime.toString()
} }
class LunaEventVH: RecyclerView.ViewHolder { class LunaEventVH: RecyclerView.ViewHolder {
val root: View
val type: TextView val type: TextView
val description: TextView val description: TextView
val quantity: TextView val quantity: TextView
val time: TextView val time: TextView
constructor(v: View) : super(v) { constructor(v: View) : super(v) {
root = v
type = v.findViewById<TextView>(R.id.type) type = v.findViewById<TextView>(R.id.type)
description = v.findViewById<TextView>(R.id.description) description = v.findViewById<TextView>(R.id.description)
quantity = v.findViewById<TextView>(R.id.quantity) quantity = v.findViewById<TextView>(R.id.quantity)
time = v.findViewById<TextView>(R.id.time) time = v.findViewById<TextView>(R.id.time)
} }
} }
interface OnItemClickListener {
fun onItemClick(event: LunaEvent)
}
} }

View File

@ -1,5 +1,7 @@
package it.danieleverducci.lunatracker.entities package it.danieleverducci.lunatracker.entities
import android.content.Context
import it.danieleverducci.lunatracker.R
import org.json.JSONObject import org.json.JSONObject
import java.util.Date import java.util.Date
@ -19,6 +21,12 @@ class LunaEvent {
val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE" val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO" val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE" val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
val TYPE_MEDICINE = "MEDICINE"
val TYPE_ENEMA = "ENEMA"
val TYPE_NOTE = "NOTE"
val TYPE_CUSTOM = "CUSTOM"
val TYPE_COLIC = "COLIC"
val TYPE_TEMPERATURE = "TEMPERATURE"
} }
private val jo: JSONObject private val jo: JSONObject
@ -39,6 +47,11 @@ class LunaEvent {
if (value > 0) if (value > 0)
jo.put("quantity", value) jo.put("quantity", value)
} }
var notes: String
get(): String = jo.optString("notes")
set(value) {
jo.put("notes", value)
}
constructor(jo: JSONObject) { constructor(jo: JSONObject) {
this.jo = jo this.jo = jo
@ -60,6 +73,46 @@ class LunaEvent {
this.quantity = quantity 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_scale_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
else -> R.string.event_unknown_type
}
)
}
fun getTypeDescription(context: Context): String {
return context.getString(
when (type) {
TYPE_BABY_BOTTLE -> R.string.event_bottle_desc
TYPE_WEIGHT -> R.string.event_scale_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
else -> R.string.event_unknown_desc
}
)
}
fun toJson(): JSONObject { fun toJson(): JSONObject {
return jo return jo
} }

View File

@ -0,0 +1,48 @@
package utils
import android.content.Context
import android.text.format.DateFormat
import it.danieleverducci.lunatracker.R
import java.util.Date
class DateUtils {
companion object {
/**
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago)
*/
fun formatTimeAgo(context: Context, unixTime: Long): String {
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
val minutesDiff = secondsDiff / 60
if (minutesDiff < 1)
return context.getString(R.string.now)
val hoursAgo = (secondsDiff / (60 * 60)).toInt()
val minutesAgo = (minutesDiff % 60).toInt()
if (hoursAgo > 24)
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
var formattedTime = StringBuilder()
if (hoursAgo > 0) {
formattedTime.append(hoursAgo).append(" ")
if (hoursAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.hour_ago))
else
formattedTime.append(context.getString(R.string.hours_ago))
}
if (minutesAgo > 0) {
if (formattedTime.isNotEmpty())
formattedTime.append(", ")
formattedTime.append(minutesAgo).append(" ")
if (minutesAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.minute_ago))
else
formattedTime.append(context.getString(R.string.minutes_ago))
}
return formattedTime.toString()
}
}
}

View File

@ -0,0 +1,92 @@
package utils
import android.content.Context
import android.icu.util.LocaleData
import android.icu.util.ULocale
import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.entities.LunaEvent
import java.text.NumberFormat
class NumericUtils (val context: Context) {
val numberFormat: NumberFormat
val measurement_unit_liquid_base: String
val measurement_unit_weight_base: String
val measurement_unit_weight_tiny: String
val measurement_unit_temperature_base: String
init {
this.numberFormat = NumberFormat.getInstance()
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
this.measurement_unit_liquid_base = context.getString(
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_liquid_base_metric
else
R.string.measurement_unit_liquid_base_imperial
)
this.measurement_unit_weight_base = context.getString(
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_weight_base_metric
else
R.string.measurement_unit_weight_base_imperial
)
this.measurement_unit_weight_tiny = context.getString(
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_weight_tiny_metric
else
R.string.measurement_unit_weight_tiny_imperial
)
this.measurement_unit_temperature_base = context.getString(
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_temperature_base_metric
else
R.string.measurement_unit_temperature_base_imperial
)
}
fun formatEventQuantity(item: LunaEvent): String {
val formatted = StringBuilder()
if ((item.quantity ?: 0) > 0) {
if (item.type == LunaEvent.TYPE_TEMPERATURE)
formatted.append((item.quantity / 10.0f).toString())
else
formatted.append(item.quantity)
formatted.append(" ")
formatted.append(
when (item.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
else -> ""
}
)
}
return formatted.toString()
}
/**
* Returns a valid quantity range for the event type.
* @return min, max, normal
*/
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
return when (lunaEventType) {
LunaEvent.TYPE_TEMPERATURE -> {
if (measurementSystem == LocaleData. MeasurementSystem.SI)
Triple(
context.resources.getInteger(R.integer.human_body_temp_min_metric),
context.resources.getInteger(R.integer.human_body_temp_max_metric),
context.resources.getInteger(R.integer.human_body_temp_normal_metric)
)
else
Triple(
context.resources.getInteger(R.integer.human_body_temp_min_imperial),
context.resources.getInteger(R.integer.human_body_temp_max_imperial),
context.resources.getInteger(R.integer.human_body_temp_normal_imperial)
)
}
else -> null
}
}
}

View File

@ -3,6 +3,8 @@
<stroke <stroke
android:width="2dp" android:width="2dp"
android:color="@color/accent" /> android:color="@color/accent" />
<solid
android:color="@color/cardview_dark_background"/>
<corners android:radius="15dp" /> <corners android:radius="15dp" />
<padding <padding
android:bottom="5dp" android:bottom="5dp"

View File

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

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="2dp"
android:color="@color/grey" />
<corners android:radius="10dp" />
<padding
android:bottom="10dp"
android:left="20dp"
android:right="20dp"
android:top="10dp" />
</shape>

View File

@ -6,7 +6,6 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:orientation="vertical"
android:paddingTop="30dp" android:paddingTop="30dp"
android:paddingLeft="15dp" android:paddingLeft="15dp"
@ -16,7 +15,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/title" android:text="@string/title"
android:textSize="30sp" android:textSize="30dp"
android:gravity="center_horizontal"/> android:gravity="center_horizontal"/>
<TextView <TextView
@ -108,7 +107,7 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_margin="10dp"
android:layout_weight="1" android:layout_weight="2"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30dp" android:textSize="30dp"
@ -119,12 +118,23 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="10dp" android:layout_margin="10dp"
android:layout_weight="1" android:layout_weight="2"
android:background="@drawable/button_background" android:background="@drawable/button_background"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textSize="30dp" android:textSize="30dp"
android:text="🚼 💧"/> android:text="🚼 💧"/>
<ImageView
android:id="@+id/button_more"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
android:src="@drawable/ic_more"
app:tint="@android:color/darker_gray"/>
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
@ -141,7 +151,7 @@
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="Diario di bordo" android:text="@string/logbook"
android:textColor="@color/accent" android:textColor="@color/accent"
android:textStyle="bold"/> android:textStyle="bold"/>

View File

@ -68,7 +68,8 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="30dp" android:layout_marginLeft="30dp"
android:hint="@string/settings_storage_dav_url_hint" android:hint="@string/settings_storage_dav_url_hint"
android:inputType="textUri"/> android:inputType="textUri"
android:background="@drawable/textview_background"/>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -83,7 +84,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="30dp" android:layout_marginLeft="30dp"
android:inputType="textEmailAddress"/> android:inputType="textEmailAddress"
android:background="@drawable/textview_background"/>
<TextView <TextView
android:layout_width="match_parent" android:layout_width="match_parent"
@ -98,7 +100,8 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="30dp" android:layout_marginLeft="30dp"
android:inputType="textPassword"/> android:inputType="textPassword"
android:background="@drawable/textview_background"/>
<com.google.android.material.progressindicator.LinearProgressIndicator <com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_indicator" android:id="@+id/progress_indicator"
@ -119,6 +122,9 @@
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"
android:layout_marginRight="20dp"
android:background="@drawable/button_background"
android:textColor="@color/accent"
android:text="@android:string/cancel"/> android:text="@android:string/cancel"/>
<Button <Button
@ -127,6 +133,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_weight="1"
android:textColor="@color/accent" android:textColor="@color/accent"
android:background="@drawable/button_background"
android:text="@android:string/ok"/> android:text="@android:string/ok"/>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<TextView
android:id="@+id/dialog_event_detail_type_emoji"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textSize="60dp"
android:text="@string/event_diaperchange_pee_type"/>
<TextView
android:id="@+id/dialog_event_detail_type_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:textColor="@color/accent"
android:textSize="24dp"
android:text="@string/event_diaperchange_pee_desc"/>
<TextView
android:id="@+id/dialog_event_detail_type_date"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center_horizontal"
android:textStyle="bold"
android:text="2020"/>
<TextView
android:id="@+id/dialog_event_detail_type_quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="2020"/>
<ScrollView
android:layout_width="match_parent"
android:layout_height="200dp"
android:layout_marginTop="20dp">
<TextView
android:id="@+id/dialog_event_detail_type_notes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="italic"
android:text="Lorem ipsum"/>
</ScrollView>
</LinearLayout>

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp">
<EditText
android:id="@+id/notes_qty_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:lines="1"
android:inputType="number"
android:hint="@string/log_notes_dialog_qty_hint"
android:background="@drawable/textview_background"/>
<EditText
android:id="@+id/notes_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="top"
android:lines="10"
android:hint="@string/log_notes_dialog_note_hint"
android:background="@drawable/textview_background"/>
</LinearLayout>

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:background="@color/transparent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/button_medicine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="20dp"
android:background="@drawable/button_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_medicine"/>
<TextView
android:id="@+id/button_enema"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="20dp"
android:background="@drawable/button_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_enema"/>
<TextView
android:id="@+id/button_note"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="20dp"
android:background="@drawable/button_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="20dp"
android:background="@drawable/button_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_temperature"/>
<TextView
android:id="@+id/button_colic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="20dp"
android:background="@drawable/button_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_colic"/>
</LinearLayout>
</ScrollView>

View File

@ -11,7 +11,8 @@
android:layout_width="150dp" android:layout_width="150dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:inputType="number" android:inputType="number"
android:hint="0"/> android:hint="0"
android:background="@drawable/textview_background"/>
<TextView <TextView
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -3,21 +3,28 @@
android:orientation="horizontal" android:orientation="horizontal"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:gravity="center_vertical"> android:gravity="center_vertical">
<TextView <TextView
android:id="@+id/type" android:id="@+id/type"
android:layout_width="0dp" android:layout_width="90dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" android:paddingLeft="10dp"
android:textSize="28sp" android:textSize="28sp"
android:text="Type"/> android:lines="1"
android:maxLines="1"
android:text="@string/event_diaperchange_pee_type"/>
<TextView <TextView
android:id="@+id/description" android:id="@+id/description"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_weight="2" android:layout_weight="2"
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/accent"
android:text="Description"/> android:text="Description"/>
<TextView <TextView
@ -25,6 +32,8 @@
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"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:text="Qty"/> android:text="Qty"/>
@ -35,6 +44,8 @@
android:layout_weight="2" android:layout_weight="2"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:textStyle="bold" android:textStyle="bold"
android:ellipsize="end"
android:maxLines="2"
android:text="Time"/> android:text="Time"/>
</LinearLayout> </LinearLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:gravity="center_vertical">
<TextView
android:id="@+id/type"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:textSize="28sp"
android:lines="1"
android:maxLines="1"
android:text="@string/event_diaperchange_pee_type"/>
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:ellipsize="end"
android:maxLines="2"
android:gravity="center_horizontal"
android:textColor="@color/accent"
android:text="Description"/>
<TextView
android:id="@+id/quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center_horizontal"
android:text="Qty"/>
<TextView
android:id="@+id/time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:gravity="center_horizontal"
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="2"
android:text="Time"/>
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<com.google.android.material.slider.Slider
android:id="@+id/dialog_temperature_value"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:valueFrom="@integer/human_body_temp_min_metric"
android:valueTo="@integer/human_body_temp_max_metric"
android:stepSize="0.1"
android:value="@integer/human_body_temp_normal_metric"
android:theme="@style/LTSlider"/>
<TextView
android:id="@+id/dialog_temperature_display"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textColor="@color/accent"/>
</LinearLayout>

View File

@ -10,14 +10,14 @@
<string name="log_weight_dialog_title">Pesata</string> <string name="log_weight_dialog_title">Pesata</string>
<string name="log_weight_dialog_description">Inserisci il peso rilevato</string> <string name="log_weight_dialog_description">Inserisci il peso rilevato</string>
<string name="event_bottle_type">🍼</string> <string name="log_temperature_dialog_title">Temperatura</string>
<string name="event_scale_type">⚖️</string> <string name="log_temperature_dialog_description">Inserisci la temperatura</string>
<string name="event_breastfeeding_left_type">🤱 ←</string>
<string name="event_breastfeeding_both_type">🤱 ↔</string> <string name="overflow_event_medicine">💊 Medicina</string>
<string name="event_breastfeeding_right_type">🤱 →</string> <string name="overflow_event_enema">🪠 Clistere</string>
<string name="event_diaperchange_poo_type">🚼 💩</string> <string name="overflow_event_note">📝 Nota</string>
<string name="event_diaperchange_pee_type">🚼 💧</string> <string name="overflow_event_temperature">🌡️ Temperatura</string>
<string name="event_unknown_type">-</string> <string name="overflow_event_colic">💨 Colichette</string>
<string name="event_bottle_desc">Biberon</string> <string name="event_bottle_desc">Biberon</string>
<string name="event_scale_desc">Pesata</string> <string name="event_scale_desc">Pesata</string>
@ -26,6 +26,11 @@
<string name="event_breastfeeding_right_desc">Allatt. al seno (dx)</string> <string name="event_breastfeeding_right_desc">Allatt. al seno (dx)</string>
<string name="event_diaperchange_poo_desc">Cambio (con cacca)</string> <string name="event_diaperchange_poo_desc">Cambio (con cacca)</string>
<string name="event_diaperchange_pee_desc">Cambio (con pipì)</string> <string name="event_diaperchange_pee_desc">Cambio (con pipì)</string>
<string name="event_medicine_desc">Medicina</string>
<string name="event_enema_desc">Clistere</string>
<string name="event_note_desc">Nota</string>
<string name="event_temperature_desc">Temperatura</string>
<string name="event_colic_desc">Colichette</string>
<string name="event_unknown_desc"></string> <string name="event_unknown_desc"></string>
<string name="toast_event_added">Evento aggiunto</string> <string name="toast_event_added">Evento aggiunto</string>
@ -49,7 +54,7 @@
<string name="settings_storage_local">Sul dispositivo</string> <string name="settings_storage_local">Sul dispositivo</string>
<string name="settings_storage_local_desc">La soluzione più privacy-friendly: i dati non escono mai dal tuo dispositivo</string> <string name="settings_storage_local_desc">La soluzione più privacy-friendly: i dati non escono mai dal tuo dispositivo</string>
<string name="settings_storage_dav">Su un server WebDAV</string> <string name="settings_storage_dav">Su un server WebDAV</string>
<string name="settings_storage_dav_desc">Puoi usare un qualunque servizio che supporti WebDAV (come ad esempio Nextcloud, Owncloud, Dropbox, Box...) per salvare i dati. In questo modo puoi sincronizzarli tra più dispositivi, ad esempio quello del papà, della mamma, dei nonni... Ti servirà l\'url WebDAV, che trovi nella documentazione del tuo provider (ad es. in Nextcloud è nelle impostazioni di Files)</string> <string name="settings_storage_dav_desc">Puoi usare un qualunque servizio che supporti WebDAV (come ad esempio Nextcloud, Owncloud, Dropbox, Box) per salvare i dati. In questo modo puoi sincronizzarli tra più dispositivi, ad esempio quello del papà, della mamma, dei nonni Ti servirà l\'url WebDAV, che trovi nella documentazione del tuo provider (ad es. in Nextcloud è nelle impostazioni di Files)</string>
<string name="settings_storage_dav_url">Url WebDAV</string> <string name="settings_storage_dav_url">Url WebDAV</string>
<string name="settings_storage_dav_url_hint">https://</string> <string name="settings_storage_dav_url_hint">https://</string>
<string name="settings_storage_dav_user">Username</string> <string name="settings_storage_dav_user">Username</string>
@ -68,4 +73,11 @@
<string name="trim_logbook_dialog_button_ok">Cancella i più vecchi</string> <string name="trim_logbook_dialog_button_ok">Cancella i più vecchi</string>
<string name="trim_logbook_dialog_button_cancel">Ricordamelo dopo</string> <string name="trim_logbook_dialog_button_cancel">Ricordamelo dopo</string>
<string name="log_notes_dialog_description">Note:</string>
<string name="log_medicine_dialog_description">Nome della medicina, quantità, formato, note…:</string>
<string name="log_notes_dialog_qty_hint">Quantità, o vuoto</string>
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
<string name="dialog_event_detail_title">Dettaglio evento</string>
</resources> </resources>

View File

@ -4,5 +4,10 @@
<color name="black">#FF000000</color> <color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color> <color name="white">#FFFFFFFF</color>
<color name="translucent">#c000</color> <color name="translucent">#c000</color>
<color name="transparent">#0000</color>
<color name="grey">#ccc</color> <color name="grey">#ccc</color>
<color name="list_background_odd">#423B25</color>
<color name="list_background_even">@color/transparent</color>
<color name="danger">#f00</color>
<color name="textColor">@color/grey</color>
</resources> </resources>

View File

@ -10,14 +10,22 @@
<string name="log_weight_dialog_title">Weight</string> <string name="log_weight_dialog_title">Weight</string>
<string name="log_weight_dialog_description">Insert the weight</string> <string name="log_weight_dialog_description">Insert the weight</string>
<string name="event_bottle_type">🍼</string> <string name="log_temperature_dialog_title">Temperature</string>
<string name="event_scale_type">⚖️</string> <string name="log_temperature_dialog_description">Insert the temperature</string>
<string name="event_breastfeeding_left_type">🤱 ←</string>
<string name="event_breastfeeding_both_type">🤱 ↔</string> <string name="event_bottle_type" translatable="false">🍼</string>
<string name="event_breastfeeding_right_type">🤱 →</string> <string name="event_scale_type" translatable="false">⚖️</string>
<string name="event_diaperchange_poo_type">🚼 💩</string> <string name="event_breastfeeding_left_type" translatable="false">🤱 ←</string>
<string name="event_diaperchange_pee_type">🚼 💧</string> <string name="event_breastfeeding_both_type" translatable="false">🤱 ↔</string>
<string name="event_unknown_type">-</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>
<string name="event_enema_type" translatable="false">🪠</string>
<string name="event_note_type" translatable="false">📝</string>
<string name="event_temperature_type" translatable="false">🌡️</string>
<string name="event_colic_type" translatable="false">💨</string>
<string name="event_unknown_type" translatable="false">\?</string>
<string name="event_bottle_desc">Baby bottle</string> <string name="event_bottle_desc">Baby bottle</string>
<string name="event_scale_desc">Weight</string> <string name="event_scale_desc">Weight</string>
@ -26,8 +34,19 @@
<string name="event_breastfeeding_right_desc">Breastfeeding (right)</string> <string name="event_breastfeeding_right_desc">Breastfeeding (right)</string>
<string name="event_diaperchange_poo_desc">Diaper chg (poo)</string> <string name="event_diaperchange_poo_desc">Diaper chg (poo)</string>
<string name="event_diaperchange_pee_desc">Diaper chg (pee)</string> <string name="event_diaperchange_pee_desc">Diaper chg (pee)</string>
<string name="event_medicine_desc">Medicine</string>
<string name="event_enema_desc">Enema</string>
<string name="event_note_desc">Note</string>
<string name="event_temperature_desc">Temperature</string>
<string name="event_colic_desc">Gaseous colic</string>
<string name="event_unknown_desc"></string> <string name="event_unknown_desc"></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="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>
<string name="toast_event_add_error">Unable to log the event</string> <string name="toast_event_add_error">Unable to log the event</string>
@ -49,7 +68,7 @@
<string name="settings_storage_local">On device</string> <string name="settings_storage_local">On device</string>
<string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string> <string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string>
<string name="settings_storage_dav">On a WebDAV server</string> <string name="settings_storage_dav">On a WebDAV server</string>
<string name="settings_storage_dav_desc">You can use any WebDAV service (i.e. Nextcloud, Owncloud, Dropbox, Box...) to save the data. In this way you can syncronize it between more devices, i.e. the dad\'s, the mom\'s, the grandma\'s... You will need the WebDAV url, you can find it in your provider\'s documentation (i.e. in Nextcloud web is in the Files app settings)</string> <string name="settings_storage_dav_desc">You can use any WebDAV service (i.e. Nextcloud, Owncloud, Dropbox, Box) to save the data. In this way you can syncronize it between more devices, i.e. the dad\'s, the mom\'s, the grandma\'s You will need the WebDAV url, you can find it in your provider\'s documentation (i.e. in Nextcloud web is in the Files app settings)</string>
<string name="settings_storage_dav_url">WebDAV url</string> <string name="settings_storage_dav_url">WebDAV url</string>
<string name="settings_storage_dav_url_hint">https://</string> <string name="settings_storage_dav_url_hint">https://</string>
<string name="settings_storage_dav_user">Username</string> <string name="settings_storage_dav_user">Username</string>
@ -68,4 +87,20 @@
<string name="trim_logbook_dialog_button_ok">Trim it now</string> <string name="trim_logbook_dialog_button_ok">Trim it now</string>
<string name="trim_logbook_dialog_button_cancel">Remind me later</string> <string name="trim_logbook_dialog_button_cancel">Remind me later</string>
<string name="log_notes_dialog_description">Notes:</string>
<string name="log_medicine_dialog_description">Medicine name, quantity, type, notes…:</string>
<string name="log_notes_dialog_qty_hint">Quantity (or empty)</string>
<string name="log_notes_dialog_note_hint">Write some notes</string>
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</string>
<string name="measurement_unit_weight_base_metric" translatable="false">g</string>
<string name="measurement_unit_weight_tiny_metric" translatable="false">mg</string>
<string name="measurement_unit_liquid_base_imperial" translatable="false">fl oz.</string>
<string name="measurement_unit_weight_base_imperial" translatable="false">oz</string>
<string name="measurement_unit_weight_tiny_imperial" translatable="false">gr</string>
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
<string name="dialog_event_detail_title">Event detail</string>
</resources> </resources>

View File

@ -1,5 +1,17 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar" /> <style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
<item name="colorAccent">@color/accent</item>
<item name="android:textColor">@color/textColor</item>
</style>
<style name="OverflowMenuText">
<item name="android:textSize">20sp</item>
</style>
<style name="LTSlider" parent="Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">@color/accent</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<integer name="human_body_temp_min_imperial">91</integer>
<integer name="human_body_temp_min_metric">33</integer>
<integer name="human_body_temp_max_imperial">109</integer>
<integer name="human_body_temp_max_metric">43</integer>
<integer name="human_body_temp_normal_imperial">98</integer>
<integer name="human_body_temp_normal_metric">37</integer>
</resources>

View File

@ -1,7 +1,5 @@
LunaTracker is a newborn baby tracking app.
Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying. Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying.
You have to react fast, event if it's 4 AM and you have no idea why is crying. You have to react fast, even if it's 4 AM and you have no idea why is crying.
This app is meant to log all the relevant events (diaper change, breastfeeding, baby bottle feeding...), so you can always remember when has been done last time. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attentions, the dad can step in leaving the mom sleeping peacefully. This app is meant to log all the relevant events (diaper change, breastfeeding, baby bottle feeding...), so you can always remember when has been done last time. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attentions, the dad can step in leaving the mom sleeping peacefully.

View File

@ -1,5 +1,3 @@
LunaTracker è un'app di tracciamento neonatale
Essere genitori può essere dura. Arrivate a casa dall'ospedale, esausti, con questo cosino fragile e sconosciuto che non ha un manuale d'uso, e ha un solo modo per segnalare che c'è qualcosa che non va: piangere. Essere genitori può essere dura. Arrivate a casa dall'ospedale, esausti, con questo cosino fragile e sconosciuto che non ha un manuale d'uso, e ha un solo modo per segnalare che c'è qualcosa che non va: piangere.
Dovete essere sempre pronti a reagire, anche se sono le 4 di mattina e non avete idea del perché stia piangendo. Dovete essere sempre pronti a reagire, anche se sono le 4 di mattina e non avete idea del perché stia piangendo.