forked from penguin86/luna-tracker
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			improvemen
			...
			a90f939428
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a90f939428 | |||
| 28674e2acc | |||
| 2d5a56188d | |||
| 36b848b95e | |||
| a1bde917f8 | |||
| 4f4ff5ed21 | |||
| 453d838470 | |||
| 34aa092722 | |||
| 961e7b90e7 | |||
| be77c7fb22 | |||
| a7c44df553 | |||
| 928112adb8 | |||
| b90dc92874 | |||
| 36481a1194 | |||
| d4adb9d981 | 
							
								
								
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.idea/.name
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
LunaTracker
 | 
			
		||||
							
								
								
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/AndroidProjectSystem.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="AndroidProjectSystem">
 | 
			
		||||
    <option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
							
								
								
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								.idea/compiler.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							@@ -0,0 +1,6 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<project version="4">
 | 
			
		||||
  <component name="CompilerConfiguration">
 | 
			
		||||
    <bytecodeTargetLevel target="21" />
 | 
			
		||||
  </component>
 | 
			
		||||
</project>
 | 
			
		||||
@@ -1,5 +1,7 @@
 | 
			
		||||
# 🌜 LunaTracker 🌛
 | 
			
		||||
 | 
			
		||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/it/packages/it.danieleverducci.lunatracker/)
 | 
			
		||||
 | 
			
		||||
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.
 | 
			
		||||
@@ -11,7 +13,8 @@ Dedicated to my daughter Luna.
 | 
			
		||||
 | 
			
		||||

 | 
			
		||||
 | 
			
		||||
Thanks for the valuable contributions to:
 | 
			
		||||
## Thanks for the valuable contributions to:
 | 
			
		||||
 | 
			
		||||
Chepycou (French translation)
 | 
			
		||||
Daniel Neubauer (German translation)
 | 
			
		||||
- Chepycou (French translation)
 | 
			
		||||
- Daniel Neubauer (German translation)
 | 
			
		||||
- Moritz Warning (Various bugfixes and new features)
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ android {
 | 
			
		||||
        applicationId = "it.danieleverducci.lunatracker"
 | 
			
		||||
        minSdk = 21
 | 
			
		||||
        targetSdk = 34
 | 
			
		||||
        versionCode = 4
 | 
			
		||||
        versionName = "0.6"
 | 
			
		||||
        versionCode = 5
 | 
			
		||||
        versionName = "0.7"
 | 
			
		||||
 | 
			
		||||
        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -41,15 +41,14 @@ import okio.IOException
 | 
			
		||||
import org.json.JSONException
 | 
			
		||||
import utils.DateUtils
 | 
			
		||||
import utils.NumericUtils
 | 
			
		||||
import java.text.DateFormat
 | 
			
		||||
import java.util.Calendar
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
class MainActivity : AppCompatActivity() {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val TAG = "MainActivity"
 | 
			
		||||
        val UPDATE_EVERY_SECS: Long = 30
 | 
			
		||||
        val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
 | 
			
		||||
        const val TAG = "MainActivity"
 | 
			
		||||
        const val UPDATE_EVERY_SECS: Long = 30
 | 
			
		||||
        const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var logbook: Logbook? = null
 | 
			
		||||
@@ -58,6 +57,7 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
    lateinit var buttonsContainer: ViewGroup
 | 
			
		||||
    lateinit var recyclerView: RecyclerView
 | 
			
		||||
    lateinit var handler: Handler
 | 
			
		||||
    var signature = ""
 | 
			
		||||
    var savingEvent = false
 | 
			
		||||
    val updateListRunnable: Runnable = Runnable {
 | 
			
		||||
        if (logbook != null && !pauseLogbookUpdate)
 | 
			
		||||
@@ -113,24 +113,24 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
        moreButton.setOnClickListener {
 | 
			
		||||
            showOverflowPopupWindow(moreButton)
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<View>(R.id.button_no_connection_settings).setOnClickListener({
 | 
			
		||||
        findViewById<View>(R.id.button_no_connection_settings).setOnClickListener {
 | 
			
		||||
            showSettings()
 | 
			
		||||
        })
 | 
			
		||||
        findViewById<View>(R.id.button_settings).setOnClickListener({
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<View>(R.id.button_settings).setOnClickListener {
 | 
			
		||||
            showSettings()
 | 
			
		||||
        })
 | 
			
		||||
        findViewById<View>(R.id.button_no_connection_retry).setOnClickListener({
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<View>(R.id.button_no_connection_retry).setOnClickListener {
 | 
			
		||||
            // This may happen at start, when logbook is still null: better ask the logbook list
 | 
			
		||||
            loadLogbookList()
 | 
			
		||||
        })
 | 
			
		||||
        findViewById<View>(R.id.button_sync).setOnClickListener({
 | 
			
		||||
        }
 | 
			
		||||
        findViewById<View>(R.id.button_sync).setOnClickListener {
 | 
			
		||||
            loadLogbookList()
 | 
			
		||||
        })
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun setListAdapter(items: ArrayList<LunaEvent>) {
 | 
			
		||||
        val adapter = LunaEventRecyclerAdapter(this, items)
 | 
			
		||||
        adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener{
 | 
			
		||||
        adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener {
 | 
			
		||||
            override fun onItemClick(event: LunaEvent) {
 | 
			
		||||
                showEventDetailDialog(event, items)
 | 
			
		||||
            }
 | 
			
		||||
@@ -169,6 +169,8 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
            logbookRepo = FileLogbookRepository()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        signature = settingsRepository.loadSignature()
 | 
			
		||||
 | 
			
		||||
        val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
 | 
			
		||||
        findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) {
 | 
			
		||||
            true -> View.GONE
 | 
			
		||||
@@ -260,6 +262,26 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
        alertDialog.show()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun askPukeValue() {
 | 
			
		||||
        val d = AlertDialog.Builder(this)
 | 
			
		||||
        val dialogView = layoutInflater.inflate(R.layout.puke_dialog, null)
 | 
			
		||||
        d.setTitle(R.string.log_puke_dialog_title)
 | 
			
		||||
        d.setMessage(R.string.log_puke_dialog_description)
 | 
			
		||||
        d.setView(dialogView)
 | 
			
		||||
 | 
			
		||||
        val spinner = dialogView.findViewById<Spinner>(R.id.dialog_puke_value)
 | 
			
		||||
        spinner.adapter = ArrayAdapter.createFromResource(this, R.array.AmountLabels, android.R.layout.simple_spinner_dropdown_item)
 | 
			
		||||
        spinner.setSelection(1)
 | 
			
		||||
 | 
			
		||||
        d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
 | 
			
		||||
            val pos = spinner.selectedItemPosition
 | 
			
		||||
            logEvent(LunaEvent(LunaEvent.TYPE_PUKE, pos))
 | 
			
		||||
        }
 | 
			
		||||
        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)
 | 
			
		||||
@@ -340,7 +362,6 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
    fun showEventDetailDialog(event: LunaEvent, items: ArrayList<LunaEvent>) {
 | 
			
		||||
        // Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
 | 
			
		||||
        pauseLogbookUpdate = true
 | 
			
		||||
        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)
 | 
			
		||||
@@ -352,8 +373,9 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
 | 
			
		||||
        val currentDateTime = Calendar.getInstance()
 | 
			
		||||
        currentDateTime.time = Date(event.time * 1000)
 | 
			
		||||
 | 
			
		||||
        val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
 | 
			
		||||
        dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time))
 | 
			
		||||
        dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
 | 
			
		||||
        dateTextView.setOnClickListener {
 | 
			
		||||
            // Show datetime picker
 | 
			
		||||
            val startYear = currentDateTime.get(Calendar.YEAR)
 | 
			
		||||
@@ -366,11 +388,9 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
                TimePickerDialog(this, { _, hour, minute ->
 | 
			
		||||
                    val pickedDateTime = Calendar.getInstance()
 | 
			
		||||
                    pickedDateTime.set(year, month, day, hour, minute)
 | 
			
		||||
                    currentDateTime.time = pickedDateTime.time
 | 
			
		||||
                    dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time))
 | 
			
		||||
 | 
			
		||||
                    // Save event and move it to the right position in the logbook
 | 
			
		||||
                    event.time = currentDateTime.time.time / 1000 // Seconds since epoch
 | 
			
		||||
                    event.time = pickedDateTime.time.time / 1000 // Seconds since epoch
 | 
			
		||||
                    dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
 | 
			
		||||
                    logbook?.sort()
 | 
			
		||||
                    recyclerView.adapter?.notifyDataSetChanged()
 | 
			
		||||
                    saveLogbook()
 | 
			
		||||
@@ -389,6 +409,13 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
            pauseLogbookUpdate = false
 | 
			
		||||
        })
 | 
			
		||||
 | 
			
		||||
        // show optional signature
 | 
			
		||||
        if (event.signature.isNotEmpty()) {
 | 
			
		||||
            val signatureTextEdit = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature)
 | 
			
		||||
            signatureTextEdit.text =  String.format(getString(R.string.dialog_event_detail_signature), event.signature)
 | 
			
		||||
            signatureTextEdit.visibility = View.VISIBLE
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // create next/previous links to events of the same type
 | 
			
		||||
 | 
			
		||||
        val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
 | 
			
		||||
@@ -638,6 +665,8 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
    fun logEvent(event: LunaEvent) {
 | 
			
		||||
        savingEvent(true)
 | 
			
		||||
 | 
			
		||||
        event.signature = signature
 | 
			
		||||
 | 
			
		||||
        setLoading(true)
 | 
			
		||||
        logbook?.logs?.add(0, event)
 | 
			
		||||
        recyclerView.adapter?.notifyItemInserted(0)
 | 
			
		||||
@@ -787,6 +816,10 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
                askTemperatureValue()
 | 
			
		||||
                dismiss()
 | 
			
		||||
            })
 | 
			
		||||
            contentView.findViewById<View>(R.id.button_puke).setOnClickListener({
 | 
			
		||||
                askPukeValue()
 | 
			
		||||
                dismiss()
 | 
			
		||||
            })
 | 
			
		||||
            contentView.findViewById<View>(R.id.button_colic).setOnClickListener({
 | 
			
		||||
                logEvent(
 | 
			
		||||
                    LunaEvent(LunaEvent.TYPE_COLIC)
 | 
			
		||||
@@ -797,6 +830,12 @@ class MainActivity : AppCompatActivity() {
 | 
			
		||||
                askWeightValue()
 | 
			
		||||
                dismiss()
 | 
			
		||||
            })
 | 
			
		||||
            contentView.findViewById<View>(R.id.button_bath).setOnClickListener({
 | 
			
		||||
                logEvent(
 | 
			
		||||
                    LunaEvent(LunaEvent.TYPE_BATH)
 | 
			
		||||
                )
 | 
			
		||||
                dismiss()
 | 
			
		||||
            })
 | 
			
		||||
        }.also { popupWindow ->
 | 
			
		||||
            popupWindow.setOnDismissListener({
 | 
			
		||||
                Handler(mainLooper).postDelayed({
 | 
			
		||||
 
 | 
			
		||||
@@ -2,6 +2,7 @@ package it.danieleverducci.lunatracker
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.widget.EditText
 | 
			
		||||
import android.widget.RadioButton
 | 
			
		||||
import android.widget.TextView
 | 
			
		||||
import android.widget.Toast
 | 
			
		||||
@@ -24,6 +25,7 @@ open class SettingsActivity : AppCompatActivity() {
 | 
			
		||||
    protected lateinit var textViewWebDAVPass: TextView
 | 
			
		||||
    protected lateinit var progressIndicator: LinearProgressIndicator
 | 
			
		||||
    protected lateinit var switchNoBreastfeeding: SwitchMaterial
 | 
			
		||||
    protected lateinit var textViewSignature: EditText
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
@@ -36,6 +38,7 @@ open class SettingsActivity : AppCompatActivity() {
 | 
			
		||||
        textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
 | 
			
		||||
        progressIndicator = findViewById(R.id.progress_indicator)
 | 
			
		||||
        switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
 | 
			
		||||
        textViewSignature = findViewById(R.id.settings_signature)
 | 
			
		||||
 | 
			
		||||
        findViewById<View>(R.id.settings_save).setOnClickListener({
 | 
			
		||||
            validateAndSave()
 | 
			
		||||
@@ -52,18 +55,20 @@ open class SettingsActivity : AppCompatActivity() {
 | 
			
		||||
        val dataRepo = settingsRepository.loadDataRepository()
 | 
			
		||||
        val webDavCredentials = settingsRepository.loadWebdavCredentials()
 | 
			
		||||
        val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
 | 
			
		||||
        val signature = settingsRepository.loadSignature()
 | 
			
		||||
 | 
			
		||||
        when (dataRepo) {
 | 
			
		||||
            LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
 | 
			
		||||
            LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        textViewSignature.setText(signature)
 | 
			
		||||
        switchNoBreastfeeding.isChecked = noBreastfeeding
 | 
			
		||||
 | 
			
		||||
        if (webDavCredentials != null) {
 | 
			
		||||
            textViewWebDAVUrl.setText(webDavCredentials[0])
 | 
			
		||||
            textViewWebDAVUser.setText(webDavCredentials[1])
 | 
			
		||||
            textViewWebDAVPass.setText(webDavCredentials[2])
 | 
			
		||||
            textViewWebDAVUrl.text = webDavCredentials[0]
 | 
			
		||||
            textViewWebDAVUser.text = webDavCredentials[1]
 | 
			
		||||
            textViewWebDAVPass.text = webDavCredentials[2]
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -156,6 +161,7 @@ open class SettingsActivity : AppCompatActivity() {
 | 
			
		||||
            else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
 | 
			
		||||
        )
 | 
			
		||||
        settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
 | 
			
		||||
        settingsRepository.saveSignature(textViewSignature.text.toString())
 | 
			
		||||
        settingsRepository.saveWebdavCredentials(
 | 
			
		||||
            textViewWebDAVUrl.text.toString(),
 | 
			
		||||
            textViewWebDAVUser.text.toString(),
 | 
			
		||||
@@ -170,7 +176,7 @@ open class SettingsActivity : AppCompatActivity() {
 | 
			
		||||
     */
 | 
			
		||||
    private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
 | 
			
		||||
        Thread(Runnable {
 | 
			
		||||
            var errors = StringBuilder()
 | 
			
		||||
            val errors = StringBuilder()
 | 
			
		||||
            val fileLogbookRepo = FileLogbookRepository()
 | 
			
		||||
            val logbooks = fileLogbookRepo.getAllLogbooks(this)
 | 
			
		||||
            for (logbook in logbooks) {
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
 | 
			
		||||
        holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
 | 
			
		||||
        // Contents
 | 
			
		||||
        holder.type.text = item.getTypeEmoji(context)
 | 
			
		||||
        holder.description.text = when(item.type) {
 | 
			
		||||
        holder.description.text = when (item.type) {
 | 
			
		||||
            LunaEvent.TYPE_MEDICINE -> item.notes
 | 
			
		||||
            LunaEvent.TYPE_NOTE -> item.notes
 | 
			
		||||
            LunaEvent.TYPE_CUSTOM -> item.notes
 | 
			
		||||
 
 | 
			
		||||
@@ -28,6 +28,8 @@ class LunaEvent: Comparable<LunaEvent> {
 | 
			
		||||
        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"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private val jo: JSONObject
 | 
			
		||||
@@ -53,6 +55,12 @@ class LunaEvent: Comparable<LunaEvent> {
 | 
			
		||||
        set(value) {
 | 
			
		||||
            jo.put("notes", value)
 | 
			
		||||
        }
 | 
			
		||||
    var signature: String
 | 
			
		||||
        get(): String = jo.optString("signature")
 | 
			
		||||
        set(value) {
 | 
			
		||||
            if (value.isNotEmpty())
 | 
			
		||||
                jo.put("signature", value)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    constructor(jo: JSONObject) {
 | 
			
		||||
        this.jo = jo
 | 
			
		||||
@@ -90,6 +98,8 @@ class LunaEvent: Comparable<LunaEvent> {
 | 
			
		||||
                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
 | 
			
		||||
                else -> R.string.event_unknown_type
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
@@ -111,6 +121,8 @@ class LunaEvent: Comparable<LunaEvent> {
 | 
			
		||||
                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
 | 
			
		||||
                else -> R.string.event_unknown_desc
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
@@ -128,7 +140,7 @@ class LunaEvent: Comparable<LunaEvent> {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun toString(): String {
 | 
			
		||||
        return "${type} qty: $quantity time: ${Date(time * 1000)}"
 | 
			
		||||
        return "$type qty: $quantity time: ${Date(time * 1000)}"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun compareTo(other: LunaEvent): Int {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,9 +13,9 @@ import java.io.FilenameFilter
 | 
			
		||||
 | 
			
		||||
class FileLogbookRepository: LogbookRepository {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val TAG = "FileLogbookRepository"
 | 
			
		||||
        val FILE_NAME_START = "data"
 | 
			
		||||
        val FILE_NAME_END = ".json"
 | 
			
		||||
        const val TAG = "FileLogbookRepository"
 | 
			
		||||
        const val FILE_NAME_START = "data"
 | 
			
		||||
        const val FILE_NAME_END = ".json"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
 | 
			
		||||
@@ -32,7 +32,7 @@ class FileLogbookRepository: LogbookRepository {
 | 
			
		||||
    fun loadLogbook(context: Context, name: String): Logbook {
 | 
			
		||||
        val logbook = Logbook(name)
 | 
			
		||||
        val fileName = getFileName(name)
 | 
			
		||||
        val file = File(context.getFilesDir(), fileName)
 | 
			
		||||
        val file = File(context.filesDir, fileName)
 | 
			
		||||
        val json = FileInputStream(file).bufferedReader().use { it.readText() }
 | 
			
		||||
        val ja = JSONArray(json)
 | 
			
		||||
        for (i in 0 until ja.length()) {
 | 
			
		||||
@@ -58,7 +58,7 @@ class FileLogbookRepository: LogbookRepository {
 | 
			
		||||
 | 
			
		||||
    fun saveLogbook(context: Context, logbook: Logbook) {
 | 
			
		||||
        val fileName = getFileName(logbook.name)
 | 
			
		||||
        val file = File(context.getFilesDir(), fileName)
 | 
			
		||||
        val file = File(context.filesDir, fileName)
 | 
			
		||||
        val ja = JSONArray()
 | 
			
		||||
        for (l in logbook.logs) {
 | 
			
		||||
            ja.put(l.toJson())
 | 
			
		||||
@@ -82,7 +82,7 @@ class FileLogbookRepository: LogbookRepository {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun listLogbooks(context: Context): ArrayList<String> {
 | 
			
		||||
        val logbooksFileNames = context.getFilesDir().list(object: FilenameFilter {
 | 
			
		||||
        val logbooksFileNames = context.filesDir.list(object: FilenameFilter {
 | 
			
		||||
            override fun accept(dir: File?, name: String?): Boolean {
 | 
			
		||||
                if (name == null)
 | 
			
		||||
                    return false
 | 
			
		||||
 
 | 
			
		||||
@@ -7,13 +7,14 @@ import androidx.core.content.edit
 | 
			
		||||
 | 
			
		||||
class LocalSettingsRepository(val context: Context) {
 | 
			
		||||
    companion object {
 | 
			
		||||
        val SHARED_PREFS_FILE_NAME = "lunasettings"
 | 
			
		||||
        val SHARED_PREFS_BB_CONTENT = "bbcontent"
 | 
			
		||||
        val SHARED_PREFS_DATA_REPO = "data_repo"
 | 
			
		||||
        val SHARED_PREFS_DAV_URL = "webdav_url"
 | 
			
		||||
        val SHARED_PREFS_DAV_USER = "webdav_user"
 | 
			
		||||
        val SHARED_PREFS_DAV_PASS = "webdav_password"
 | 
			
		||||
        val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
 | 
			
		||||
        const val SHARED_PREFS_FILE_NAME = "lunasettings"
 | 
			
		||||
        const val SHARED_PREFS_BB_CONTENT = "bbcontent"
 | 
			
		||||
        const val SHARED_PREFS_DATA_REPO = "data_repo"
 | 
			
		||||
        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_SIGNATURE = "signature"
 | 
			
		||||
    }
 | 
			
		||||
    enum class DATA_REPO {LOCAL_FILE, WEBDAV}
 | 
			
		||||
    val sharedPreferences: SharedPreferences
 | 
			
		||||
@@ -23,15 +24,23 @@ class LocalSettingsRepository(val context: Context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveBabyBottleContent(content: Int) {
 | 
			
		||||
        sharedPreferences.edit().putInt(SHARED_PREFS_BB_CONTENT, content).apply()
 | 
			
		||||
        sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadBabyBottleContent(): Int {
 | 
			
		||||
        return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveSignature(content: String) {
 | 
			
		||||
        sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadSignature(): String {
 | 
			
		||||
        return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveNoBreastfeeding(content: Boolean) {
 | 
			
		||||
        sharedPreferences.edit().putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content).apply()
 | 
			
		||||
        sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadNoBreastfeeding(): Boolean {
 | 
			
		||||
@@ -39,15 +48,15 @@ class LocalSettingsRepository(val context: Context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveDataRepository(repo: DATA_REPO) {
 | 
			
		||||
        val spe = sharedPreferences.edit()
 | 
			
		||||
        spe.putString(
 | 
			
		||||
        sharedPreferences.edit(commit = true) {
 | 
			
		||||
            putString(
 | 
			
		||||
                SHARED_PREFS_DATA_REPO,
 | 
			
		||||
                when (repo) {
 | 
			
		||||
                    DATA_REPO.WEBDAV -> "webdav"
 | 
			
		||||
                    DATA_REPO.LOCAL_FILE -> "localfile"
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
        spe.commit()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadDataRepository(): DATA_REPO {
 | 
			
		||||
@@ -60,11 +69,11 @@ class LocalSettingsRepository(val context: Context) {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun saveWebdavCredentials(url: String, username: String, password: String) {
 | 
			
		||||
        val spe = sharedPreferences.edit()
 | 
			
		||||
        spe.putString(SHARED_PREFS_DAV_URL, url)
 | 
			
		||||
        spe.putString(SHARED_PREFS_DAV_USER, username)
 | 
			
		||||
        spe.putString(SHARED_PREFS_DAV_PASS, password)
 | 
			
		||||
        spe.commit()
 | 
			
		||||
        sharedPreferences.edit(commit = true) {
 | 
			
		||||
            putString(SHARED_PREFS_DAV_URL, url)
 | 
			
		||||
            putString(SHARED_PREFS_DAV_USER, username)
 | 
			
		||||
            putString(SHARED_PREFS_DAV_PASS, password)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun loadWebdavCredentials(): Array<String>? {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,12 +1,17 @@
 | 
			
		||||
package utils
 | 
			
		||||
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import android.text.format.DateFormat
 | 
			
		||||
import it.danieleverducci.lunatracker.R
 | 
			
		||||
import java.util.Date
 | 
			
		||||
 | 
			
		||||
class DateUtils {
 | 
			
		||||
    companion object {
 | 
			
		||||
        /**
 | 
			
		||||
         * Format time duration in seconds as e.g. "2 hours, 1 min".
 | 
			
		||||
         * Used for the duration to the next/previous event in the event details dialog.
 | 
			
		||||
         */
 | 
			
		||||
        fun formatTimeDuration(context: Context, secondsDiff: Long): String {
 | 
			
		||||
            var seconds = secondsDiff
 | 
			
		||||
 | 
			
		||||
@@ -65,7 +70,8 @@ class DateUtils {
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago)
 | 
			
		||||
         * Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago".
 | 
			
		||||
         * Used for the event list.
 | 
			
		||||
         */
 | 
			
		||||
        fun formatTimeAgo(context: Context, unixTime: Long): String {
 | 
			
		||||
            val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
 | 
			
		||||
@@ -100,5 +106,21 @@ class DateUtils {
 | 
			
		||||
            }
 | 
			
		||||
            return formattedTime.toString()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Format time as localized string without seconds. E.g. "Sept 18, 2025, 03:36 PM".
 | 
			
		||||
         * Used in the event detail dialog.
 | 
			
		||||
         */
 | 
			
		||||
        fun formatDateTime(unixTime: Long): String {
 | 
			
		||||
            val date = Date(unixTime * 1000)
 | 
			
		||||
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
 | 
			
		||||
                val dateFormat = android.icu.text.DateFormat.getDateTimeInstance(android.icu.text.DateFormat.RELATIVE_SHORT, android.icu.text.DateFormat.SHORT)
 | 
			
		||||
                return dateFormat.format(date)
 | 
			
		||||
            } else {
 | 
			
		||||
                // fallback
 | 
			
		||||
                val dateFormat = java.text.DateFormat.getDateTimeInstance()
 | 
			
		||||
                return dateFormat.format(date)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -3,6 +3,7 @@ package utils
 | 
			
		||||
import android.content.Context
 | 
			
		||||
import android.icu.util.LocaleData
 | 
			
		||||
import android.icu.util.ULocale
 | 
			
		||||
import android.os.Build
 | 
			
		||||
import it.danieleverducci.lunatracker.R
 | 
			
		||||
import it.danieleverducci.lunatracker.entities.LunaEvent
 | 
			
		||||
import java.text.NumberFormat
 | 
			
		||||
@@ -14,29 +15,45 @@ class NumericUtils (val context: Context) {
 | 
			
		||||
    val measurement_unit_weight_tiny: String
 | 
			
		||||
    val measurement_unit_temperature_base: String
 | 
			
		||||
 | 
			
		||||
    private fun isMetricSystem(): Boolean {
 | 
			
		||||
       if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
 | 
			
		||||
            val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
 | 
			
		||||
            return (measurementSystem == LocaleData.MeasurementSystem.SI)
 | 
			
		||||
       } else {
 | 
			
		||||
            val locale = context.resources.configuration.locale
 | 
			
		||||
            return when (locale.country) {
 | 
			
		||||
                // https://en.wikipedia.org/wiki/United_States_customary_units
 | 
			
		||||
                // https://en.wikipedia.org/wiki/Imperial_units
 | 
			
		||||
                "US" -> false // US IMPERIAL
 | 
			
		||||
                // UK, Myanmar, Liberia,
 | 
			
		||||
                "GB", "MM", "LR" -> false // IMPERIAL
 | 
			
		||||
                else -> true // METRIC
 | 
			
		||||
            }
 | 
			
		||||
       }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        this.numberFormat = NumberFormat.getInstance()
 | 
			
		||||
        val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
 | 
			
		||||
        this.measurement_unit_liquid_base = context.getString(
 | 
			
		||||
            if (measurementSystem == LocaleData. MeasurementSystem.SI)
 | 
			
		||||
            if (isMetricSystem())
 | 
			
		||||
                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)
 | 
			
		||||
            if (isMetricSystem())
 | 
			
		||||
                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)
 | 
			
		||||
            if (isMetricSystem())
 | 
			
		||||
                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)
 | 
			
		||||
            if (isMetricSystem())
 | 
			
		||||
                R.string.measurement_unit_temperature_base_metric
 | 
			
		||||
            else
 | 
			
		||||
                R.string.measurement_unit_temperature_base_imperial
 | 
			
		||||
@@ -45,11 +62,15 @@ class NumericUtils (val context: Context) {
 | 
			
		||||
 | 
			
		||||
    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)
 | 
			
		||||
        if (item.quantity > 0) {
 | 
			
		||||
            formatted.append(when (item.type) {
 | 
			
		||||
                LunaEvent.TYPE_TEMPERATURE ->
 | 
			
		||||
                    (item.quantity / 10.0f).toString()
 | 
			
		||||
                LunaEvent.TYPE_PUKE ->
 | 
			
		||||
                    context.resources.getStringArray(R.array.AmountLabels)[item.quantity]
 | 
			
		||||
                else ->
 | 
			
		||||
                    item.quantity
 | 
			
		||||
            })
 | 
			
		||||
 | 
			
		||||
            formatted.append(" ")
 | 
			
		||||
            formatted.append(
 | 
			
		||||
@@ -70,10 +91,9 @@ class NumericUtils (val context: Context) {
 | 
			
		||||
     * @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)
 | 
			
		||||
                if (isMetricSystem())
 | 
			
		||||
                    Triple(
 | 
			
		||||
                        context.resources.getInteger(R.integer.human_body_temp_min_metric),
 | 
			
		||||
                        context.resources.getInteger(R.integer.human_body_temp_max_metric),
 | 
			
		||||
 
 | 
			
		||||
@@ -119,28 +119,57 @@
 | 
			
		||||
                android:visibility="invisible"/>
 | 
			
		||||
        </RadioGroup>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:layout_width="wrap_content"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:textStyle="bold"
 | 
			
		||||
            android:text="@string/settings_signature" />
 | 
			
		||||
 | 
			
		||||
        <EditText
 | 
			
		||||
            android:id="@+id/settings_signature"
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="20dp"
 | 
			
		||||
            android:layout_marginTop="5dp"
 | 
			
		||||
            android:inputType="textEmailAddress"
 | 
			
		||||
            android:background="@drawable/textview_background"/>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="30dp"
 | 
			
		||||
            android:layout_marginTop="5dp"
 | 
			
		||||
            android:text="@string/settings_signature_desc"/>
 | 
			
		||||
 | 
			
		||||
        <LinearLayout
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:orientation="horizontal"
 | 
			
		||||
            android:layout_marginTop="5dp"
 | 
			
		||||
            android:layout_marginEnd="30dp">
 | 
			
		||||
            android:layout_marginTop="20dp">
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:layout_width="0dp"
 | 
			
		||||
                android:layout_weight="1"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:textStyle="bold"
 | 
			
		||||
                android:text="@string/no_breastfeeding" />
 | 
			
		||||
                android:text="@string/settings_no_breastfeeding" />
 | 
			
		||||
 | 
			
		||||
            <com.google.android.material.switchmaterial.SwitchMaterial
 | 
			
		||||
                android:id="@+id/switch_no_breastfeeding"
 | 
			
		||||
                android:layout_width="0dp"
 | 
			
		||||
                android:layout_width="wrap_content"
 | 
			
		||||
                android:layout_height="wrap_content"
 | 
			
		||||
                android:layout_marginEnd="20dp"
 | 
			
		||||
                android:layout_weight="1" />
 | 
			
		||||
 | 
			
		||||
        </LinearLayout>
 | 
			
		||||
 | 
			
		||||
        <TextView
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
            android:layout_marginStart="30dp"
 | 
			
		||||
            android:layout_marginTop="5dp"
 | 
			
		||||
            android:text="@string/settings_no_breastfeeding_desc"/>
 | 
			
		||||
 | 
			
		||||
        <LinearLayout
 | 
			
		||||
            android:layout_width="match_parent"
 | 
			
		||||
            android:layout_height="wrap_content"
 | 
			
		||||
 
 | 
			
		||||
@@ -61,6 +61,14 @@
 | 
			
		||||
 | 
			
		||||
    </ScrollView>
 | 
			
		||||
 | 
			
		||||
    <TextView
 | 
			
		||||
        android:layout_width="wrap_content"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:layout_gravity="center_horizontal"
 | 
			
		||||
        android:id="@+id/dialog_event_detail_type_signature"
 | 
			
		||||
        android:layout_marginBottom="5dp"
 | 
			
		||||
        android:visibility="gone"/>
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
        android:layout_width="match_parent"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@
 | 
			
		||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    android:layout_width="match_parent"
 | 
			
		||||
    android:layout_height="match_parent"
 | 
			
		||||
    android:padding="20dp"
 | 
			
		||||
    android:padding="10dp"
 | 
			
		||||
    android:background="@color/transparent">
 | 
			
		||||
 | 
			
		||||
    <LinearLayout
 | 
			
		||||
@@ -14,27 +14,17 @@
 | 
			
		||||
                android:id="@+id/button_medicine"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:padding="20dp"
 | 
			
		||||
                android:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_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/dropdown_list_item_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:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_background"
 | 
			
		||||
                style="@style/OverflowMenuText"
 | 
			
		||||
                android:text="@string/overflow_event_note"/>
 | 
			
		||||
@@ -44,17 +34,27 @@
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:layout_marginTop="10dp"
 | 
			
		||||
                android:padding="20dp"
 | 
			
		||||
                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_colic"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:layout_marginTop="10dp"
 | 
			
		||||
                android:padding="20dp"
 | 
			
		||||
                android:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_background"
 | 
			
		||||
                style="@style/OverflowMenuText"
 | 
			
		||||
                android:text="@string/overflow_event_colic"/>
 | 
			
		||||
@@ -64,11 +64,31 @@
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:layout_marginTop="10dp"
 | 
			
		||||
                android:padding="20dp"
 | 
			
		||||
                android:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_background"
 | 
			
		||||
                style="@style/OverflowMenuText"
 | 
			
		||||
                android:text="@string/overflow_event_scale"/>
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/button_bath"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:layout_marginTop="10dp"
 | 
			
		||||
                android:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_background"
 | 
			
		||||
                style="@style/OverflowMenuText"
 | 
			
		||||
                android:text="@string/overflow_event_bath"/>
 | 
			
		||||
 | 
			
		||||
            <TextView
 | 
			
		||||
                android:id="@+id/button_enema"
 | 
			
		||||
                android:layout_width="match_parent"
 | 
			
		||||
                android:layout_height="match_parent"
 | 
			
		||||
                android:layout_marginTop="10dp"
 | 
			
		||||
                android:padding="10dp"
 | 
			
		||||
                android:background="@drawable/dropdown_list_item_background"
 | 
			
		||||
                style="@style/OverflowMenuText"
 | 
			
		||||
                android:text="@string/overflow_event_enema"/>
 | 
			
		||||
 | 
			
		||||
    </LinearLayout>
 | 
			
		||||
 | 
			
		||||
</ScrollView>
 | 
			
		||||
							
								
								
									
										17
									
								
								app/src/main/res/layout/puke_dialog.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/src/main/res/layout/puke_dialog.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
<?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">
 | 
			
		||||
 | 
			
		||||
    <Spinner
 | 
			
		||||
        android:id="@+id/dialog_puke_value"
 | 
			
		||||
        android:layout_width="250dp"
 | 
			
		||||
        android:layout_height="wrap_content"
 | 
			
		||||
        android:paddingHorizontal="16dp"
 | 
			
		||||
        android:paddingVertical="8dp"/>
 | 
			
		||||
 | 
			
		||||
</LinearLayout>
 | 
			
		||||
@@ -50,9 +50,8 @@
 | 
			
		||||
    <string name="no_connection_go_to_settings">Einstellungen</string>
 | 
			
		||||
    <string name="no_connection_retry">Erneut versuchen</string>
 | 
			
		||||
 | 
			
		||||
    <string name="no_breastfeeding">Kein Stillen</string>
 | 
			
		||||
 | 
			
		||||
    <string name="settings_title">Einstellungen</string>
 | 
			
		||||
    <string name="settings_no_breastfeeding">Kein Stillen</string>
 | 
			
		||||
    <string name="settings_storage">Speicherort für Daten auswählen</string>
 | 
			
		||||
    <string name="settings_storage_local">Auf dem Gerät</string>
 | 
			
		||||
    <string name="settings_storage_local_desc">Datenschutzfreundlichste Lösung: Deine Daten verlassen dein Gerät nicht</string>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								app/src/main/res/values/arrays.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								app/src/main/res/values/arrays.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,8 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<resources>
 | 
			
		||||
    <string-array name="AmountLabels">
 | 
			
		||||
        <item>@string/amount_little</item>
 | 
			
		||||
        <item>@string/amount_normal</item>
 | 
			
		||||
        <item>@string/amount_plenty</item>
 | 
			
		||||
    </string-array>
 | 
			
		||||
</resources>
 | 
			
		||||
@@ -12,6 +12,9 @@
 | 
			
		||||
    <string name="log_temperature_dialog_title">Temperature</string>
 | 
			
		||||
    <string name="log_temperature_dialog_description">Insert the temperature</string>
 | 
			
		||||
 | 
			
		||||
    <string name="log_puke_dialog_title">Puke</string>
 | 
			
		||||
    <string name="log_puke_dialog_description">Select the amount</string>
 | 
			
		||||
 | 
			
		||||
    <string name="event_bottle_type" translatable="false">🍼</string>
 | 
			
		||||
    <string name="event_food_type" translatable="false">🥣</string>
 | 
			
		||||
    <string name="event_scale_type" translatable="false">⚖️</string>
 | 
			
		||||
@@ -25,6 +28,8 @@
 | 
			
		||||
    <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_puke_type" translatable="false">🤮</string>
 | 
			
		||||
    <string name="event_bath_type" translatable="false">🛁</string>
 | 
			
		||||
    <string name="event_unknown_type" translatable="false">\?</string>
 | 
			
		||||
 | 
			
		||||
    <string name="event_bottle_desc">Baby bottle</string>
 | 
			
		||||
@@ -40,6 +45,8 @@
 | 
			
		||||
    <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_puke_desc">Puke</string>
 | 
			
		||||
    <string name="event_bath_desc">Bath</string>
 | 
			
		||||
    <string name="event_unknown_desc"></string>
 | 
			
		||||
 | 
			
		||||
    <string name="overflow_event_scale">⚖️ Weight</string>
 | 
			
		||||
@@ -48,6 +55,8 @@
 | 
			
		||||
    <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_bath">🛁 Bath</string>
 | 
			
		||||
 | 
			
		||||
    <string name="toast_event_added">Event logged</string>
 | 
			
		||||
    <string name="toast_logbook_saved">Logbook saved</string>
 | 
			
		||||
@@ -66,14 +75,18 @@
 | 
			
		||||
    <string name="year_ago">year</string>
 | 
			
		||||
    <string name="years_ago">years</string>
 | 
			
		||||
 | 
			
		||||
    <string name="amount_little">Little</string>
 | 
			
		||||
    <string name="amount_normal">Normal</string>
 | 
			
		||||
    <string name="amount_plenty">Plenty</string>
 | 
			
		||||
 | 
			
		||||
    <string name="no_connection">No connection</string>
 | 
			
		||||
    <string name="no_connection_explain">Unable to reach WebDAV service</string>
 | 
			
		||||
    <string name="no_connection_go_to_settings">Settings</string>
 | 
			
		||||
    <string name="no_connection_retry">Retry</string>
 | 
			
		||||
 | 
			
		||||
    <string name="no_breastfeeding">No Breastfeeding</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>
 | 
			
		||||
    <string name="settings_storage">Choose where to save data</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>
 | 
			
		||||
@@ -89,6 +102,8 @@
 | 
			
		||||
    <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>
 | 
			
		||||
@@ -123,6 +138,7 @@
 | 
			
		||||
    <string name="dialog_event_detail_delete_button">Delete</string>
 | 
			
		||||
    <string name="dialog_event_detail_quantity">Quantity</string>
 | 
			
		||||
    <string name="dialog_event_detail_notes">Notes</string>
 | 
			
		||||
    <string name="dialog_event_detail_signature">by %s</string>
 | 
			
		||||
 | 
			
		||||
    <string name="dialog_add_logbook_title">Add logbook</string>
 | 
			
		||||
    <string name="dialog_add_logbook_logbookname">👶 Logbook name</string>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,7 @@ This app is meant to log all the relevant events (diaper change, breastfeeding,
 | 
			
		||||
Dedicated to my daughter Luna.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
A HUGE thanks to all our contributors. See https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
 | 
			
		||||
 | 
			
		||||
NOTE: The content on this app is for informational or educational purposes only and does not substitute professional medical advice or consultations with healthcare professionals.
 | 
			
		||||
Feature graphic ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@ Elle permet de synchroniser les données entre différents appareils (en utilisa
 | 
			
		||||
Dédié à ma fille Luna.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Un grand merci à tous nos contributeurs. Voir https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
 | 
			
		||||
 | 
			
		||||
REMARQUE : le contenu de cette application est fourni à titre informatif ou éducatif uniquement et ne remplace pas les conseils médicaux professionnels ou les consultations avec des professionnels de la santé.
 | 
			
		||||
 | 
			
		||||
Crédit image ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons
 | 
			
		||||
@@ -6,6 +6,7 @@ Quest'app è pensata per memorizzare tutti gli eventi del bambino (cambio di pan
 | 
			
		||||
Dedicato a mia figlia Luna.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Un grandissimo ringraziamento a tutti i contributors! Vedi https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
 | 
			
		||||
 | 
			
		||||
NOTA: il contenuto di quest'app ha solo scopo informativo o didattico e non sostituisce il consulto medico professionale o le consulenze con operatori sanitari.
 | 
			
		||||
Feature graphic ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,5 @@
 | 
			
		||||
[versions]
 | 
			
		||||
agp = "8.7.2"
 | 
			
		||||
agp = "8.13.0"
 | 
			
		||||
kotlin = "2.0.0"
 | 
			
		||||
coreKtx = "1.10.1"
 | 
			
		||||
junit = "4.13.2"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								gradle/wrapper/gradle-wrapper.properties
									
									
									
									
										vendored
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
#Sat Nov 02 10:58:51 CET 2024
 | 
			
		||||
distributionBase=GRADLE_USER_HOME
 | 
			
		||||
distributionPath=wrapper/dists
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
 | 
			
		||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
 | 
			
		||||
zipStoreBase=GRADLE_USER_HOME
 | 
			
		||||
zipStorePath=wrapper/dists
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user