Compare commits
50 Commits
multiple-c
...
2355dd4390
| Author | SHA1 | Date | |
|---|---|---|---|
| 2355dd4390 | |||
| 587fc5d3e3 | |||
| 193e21ce25 | |||
| 7f67c758c9 | |||
| dfa64d71a8 | |||
| b7180068f3 | |||
| 36b848b95e | |||
| a1bde917f8 | |||
| 4f4ff5ed21 | |||
| 453d838470 | |||
| 34aa092722 | |||
| 961e7b90e7 | |||
| be77c7fb22 | |||
| a7c44df553 | |||
| 928112adb8 | |||
| b90dc92874 | |||
| 36481a1194 | |||
| d4adb9d981 | |||
| 5df3b31e64 | |||
| 0a96fb91d7 | |||
| f4c526ff8d | |||
| ea88fd6bb6 | |||
| 3e2bdca083 | |||
| e5e486c315 | |||
| 3f648a6490 | |||
| cc6ebc398c | |||
| e1f48e6524 | |||
| 5e1c67d52d | |||
| 3a457b2ae6 | |||
| a2fd04d233 | |||
| f1f73b65f7 | |||
| 5c247f5948 | |||
| 83b77b40d6 | |||
| 25f7b1fc00 | |||
| a635e46180 | |||
| 1080fea3f2 | |||
| 6232aedd04 | |||
| 30dc958a0c | |||
|
|
1de99e89e5 | ||
| 05c34178a4 | |||
| 0d3be20e1e | |||
| 4c5c7bcf1a | |||
| 07393faf41 | |||
| 043a1c0283 | |||
| 9fddd37fe9 | |||
| efdb8f584a | |||
| 1511764497 | |||
| 744bfef62b | |||
| 34ca6c1cd6 | |||
| b4f47ea6bb |
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 🌛
|
# 🌜 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.
|
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.
|
||||||
@@ -10,3 +12,9 @@ This app is meant to log all the relevant events (diaper change, breastfeeding,
|
|||||||
Dedicated to my daughter Luna.
|
Dedicated to my daughter Luna.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
## Thanks for the valuable contributions to:
|
||||||
|
|
||||||
|
- Chepycou (French translation)
|
||||||
|
- Daniel Neubauer (German translation)
|
||||||
|
- Moritz Warning (Various bugfixes and new features)
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ android {
|
|||||||
applicationId = "it.danieleverducci.lunatracker"
|
applicationId = "it.danieleverducci.lunatracker"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 34
|
||||||
versionCode = 2
|
versionCode = 5
|
||||||
versionName = "0.3"
|
versionName = "0.7"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="@string/settings_title"
|
android:label="@string/settings_title"
|
||||||
android:theme="@style/Theme.LunaTracker"/>
|
android:theme="@style/Theme.LunaTracker"/>
|
||||||
|
<activity
|
||||||
|
android:name=".StatisticsActivity"
|
||||||
|
android:label="@string/statistics_title"
|
||||||
|
android:theme="@style/Theme.LunaTracker"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
package it.danieleverducci.lunatracker
|
package it.danieleverducci.lunatracker
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog
|
||||||
|
import android.app.TimePickerDialog
|
||||||
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.res.ColorStateList
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@@ -18,12 +22,12 @@ import android.widget.Toast
|
|||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.children
|
|
||||||
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.google.android.material.slider.Slider
|
||||||
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||||
|
import it.danieleverducci.lunatracker.adapters.DaySeparatorDecoration
|
||||||
import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter
|
import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter
|
||||||
import it.danieleverducci.lunatracker.entities.Logbook
|
import it.danieleverducci.lunatracker.entities.Logbook
|
||||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
@@ -37,70 +41,85 @@ 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.DateUtils
|
||||||
import utils.NumericUtils
|
import utils.NumericUtils
|
||||||
import java.text.DateFormat
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "MainActivity"
|
const val TAG = "MainActivity"
|
||||||
val UPDATE_EVERY_SECS: Long = 30
|
const val UPDATE_EVERY_SECS: Long = 30
|
||||||
val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
|
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
|
||||||
}
|
}
|
||||||
|
|
||||||
lateinit var logbook: Logbook
|
var logbook: Logbook? = null
|
||||||
lateinit var adapter: LunaEventRecyclerAdapter
|
var pauseLogbookUpdate = false
|
||||||
lateinit var progressIndicator: LinearProgressIndicator
|
lateinit var progressIndicator: LinearProgressIndicator
|
||||||
lateinit var buttonsContainer: ViewGroup
|
lateinit var buttonsContainer: ViewGroup
|
||||||
lateinit var recyclerView: RecyclerView
|
lateinit var recyclerView: RecyclerView
|
||||||
lateinit var handler: Handler
|
lateinit var handler: Handler
|
||||||
|
var signature = ""
|
||||||
var savingEvent = false
|
var savingEvent = false
|
||||||
val updateListRunnable: Runnable = Runnable {
|
val updateListRunnable: Runnable = Runnable {
|
||||||
loadLogbook(logbook.name)
|
if (logbook != null && !pauseLogbookUpdate)
|
||||||
|
loadLogbook(logbook!!.name)
|
||||||
handler.postDelayed(updateListRunnable, 1000*60)
|
handler.postDelayed(updateListRunnable, 1000*60)
|
||||||
}
|
}
|
||||||
var logbookRepo: LogbookRepository? = null
|
var logbookRepo: LogbookRepository? = null
|
||||||
var showingOverflowPopupWindow = false
|
var showingOverflowPopupWindow = false
|
||||||
|
|
||||||
|
// Breastfeeding timer state
|
||||||
|
var bfTimerStartTime: Long = 0
|
||||||
|
var bfTimerType: String? = null
|
||||||
|
var bfTimerDialog: AlertDialog? = null
|
||||||
|
var bfTimerHandler: Handler? = null
|
||||||
|
var bfTimerRunnable: Runnable? = null
|
||||||
|
|
||||||
|
// Sleep timer state
|
||||||
|
var sleepTimerStartTime: Long = 0
|
||||||
|
var sleepTimerDialog: AlertDialog? = null
|
||||||
|
var sleepTimerHandler: Handler? = null
|
||||||
|
var sleepTimerRunnable: Runnable? = null
|
||||||
|
|
||||||
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.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)
|
||||||
|
|
||||||
progressIndicator = findViewById<LinearProgressIndicator>(R.id.progress_indicator)
|
progressIndicator = findViewById(R.id.progress_indicator)
|
||||||
buttonsContainer = findViewById<ViewGroup>(R.id.buttons_container)
|
buttonsContainer = findViewById(R.id.buttons_container)
|
||||||
recyclerView = findViewById<RecyclerView>(R.id.list_events)
|
recyclerView = findViewById(R.id.list_events)
|
||||||
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
|
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
|
||||||
recyclerView.adapter = adapter
|
|
||||||
|
|
||||||
// Set listeners
|
// Set listeners
|
||||||
findViewById<View>(R.id.logbooks_add_button).setOnClickListener { showAddLogbookDialog(true) }
|
findViewById<View>(R.id.logbooks_add_button).setOnClickListener { showAddLogbookDialog(true) }
|
||||||
findViewById<View>(R.id.button_bottle).setOnClickListener { askBabyBottleContent() }
|
findViewById<View>(R.id.button_bottle).setOnClickListener { askBabyBottleContent() }
|
||||||
findViewById<View>(R.id.button_scale).setOnClickListener { askWeightValue() }
|
findViewById<View>(R.id.button_food).setOnClickListener { askNotes(LunaEvent(LunaEvent.TYPE_FOOD)) }
|
||||||
findViewById<View>(R.id.button_nipple_left).setOnClickListener { logEvent(
|
findViewById<View>(R.id.button_nipple_left).setOnClickListener {
|
||||||
LunaEvent(
|
startBreastfeedingTimer(LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE)
|
||||||
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE
|
}
|
||||||
)
|
findViewById<View>(R.id.button_nipple_left).setOnLongClickListener {
|
||||||
) }
|
askBreastfeedingDuration(LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE)
|
||||||
findViewById<View>(R.id.button_nipple_both).setOnClickListener { logEvent(
|
true
|
||||||
LunaEvent(
|
}
|
||||||
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE
|
findViewById<View>(R.id.button_nipple_both).setOnClickListener {
|
||||||
)
|
startBreastfeedingTimer(LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE)
|
||||||
) }
|
}
|
||||||
findViewById<View>(R.id.button_nipple_right).setOnClickListener { logEvent(
|
findViewById<View>(R.id.button_nipple_both).setOnLongClickListener {
|
||||||
LunaEvent(
|
askBreastfeedingDuration(LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE)
|
||||||
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE
|
true
|
||||||
)
|
}
|
||||||
) }
|
findViewById<View>(R.id.button_nipple_right).setOnClickListener {
|
||||||
|
startBreastfeedingTimer(LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE)
|
||||||
|
}
|
||||||
|
findViewById<View>(R.id.button_nipple_right).setOnLongClickListener {
|
||||||
|
askBreastfeedingDuration(LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE)
|
||||||
|
true
|
||||||
|
}
|
||||||
findViewById<View>(R.id.button_change_poo).setOnClickListener { logEvent(
|
findViewById<View>(R.id.button_change_poo).setOnClickListener { logEvent(
|
||||||
LunaEvent(
|
LunaEvent(
|
||||||
LunaEvent.TYPE_DIAPERCHANGE_POO
|
LunaEvent.TYPE_DIAPERCHANGE_POO
|
||||||
@@ -115,18 +134,38 @@ class MainActivity : AppCompatActivity() {
|
|||||||
moreButton.setOnClickListener {
|
moreButton.setOnClickListener {
|
||||||
showOverflowPopupWindow(moreButton)
|
showOverflowPopupWindow(moreButton)
|
||||||
}
|
}
|
||||||
findViewById<View>(R.id.button_no_connection_settings).setOnClickListener({
|
findViewById<View>(R.id.button_no_connection_settings).setOnClickListener {
|
||||||
showSettings()
|
showSettings()
|
||||||
})
|
}
|
||||||
findViewById<View>(R.id.button_settings).setOnClickListener({
|
findViewById<View>(R.id.button_settings).setOnClickListener {
|
||||||
showSettings()
|
showSettings()
|
||||||
})
|
}
|
||||||
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener({
|
findViewById<View>(R.id.button_statistics).setOnClickListener {
|
||||||
loadLogbook(logbook.name)
|
showStatistics()
|
||||||
})
|
}
|
||||||
findViewById<View>(R.id.button_sync).setOnClickListener({
|
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener {
|
||||||
loadLogbook(logbook.name)
|
// This may happen at start, when logbook is still null: better ask the logbook list
|
||||||
})
|
loadLogbookList()
|
||||||
|
}
|
||||||
|
findViewById<View>(R.id.button_sync).setOnClickListener {
|
||||||
|
loadLogbookList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setListAdapter(items: ArrayList<LunaEvent>) {
|
||||||
|
val adapter = LunaEventRecyclerAdapter(this, items)
|
||||||
|
adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener {
|
||||||
|
override fun onItemClick(event: LunaEvent) {
|
||||||
|
showEventDetailDialog(event, items)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
recyclerView.adapter = adapter
|
||||||
|
|
||||||
|
// Tages-Trenner hinzufügen
|
||||||
|
while (recyclerView.itemDecorationCount > 0) {
|
||||||
|
recyclerView.removeItemDecorationAt(0)
|
||||||
|
}
|
||||||
|
recyclerView.addItemDecoration(DaySeparatorDecoration(this, items))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showSettings() {
|
fun showSettings() {
|
||||||
@@ -134,11 +173,18 @@ class MainActivity : AppCompatActivity() {
|
|||||||
startActivity(i)
|
startActivity(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun showStatistics() {
|
||||||
|
val i = Intent(this, StatisticsActivity::class.java)
|
||||||
|
i.putExtra(StatisticsActivity.EXTRA_LOGBOOK_NAME, logbook?.name ?: "")
|
||||||
|
startActivity(i)
|
||||||
|
}
|
||||||
|
|
||||||
fun showLogbook() {
|
fun showLogbook() {
|
||||||
// Show logbook
|
// Show logbook
|
||||||
adapter.items.clear()
|
if (logbook == null)
|
||||||
adapter.items.addAll(logbook.logs)
|
Log.w(TAG, "showLogbook(): logbook is null!")
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
|
setListAdapter(logbook?.logs ?: arrayListOf())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@@ -159,16 +205,43 @@ class MainActivity : AppCompatActivity() {
|
|||||||
logbookRepo = FileLogbookRepository()
|
logbookRepo = FileLogbookRepository()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update list dates
|
signature = settingsRepository.loadSignature()
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
|
|
||||||
// Reload data
|
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
|
||||||
|
findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) {
|
||||||
|
true -> View.GONE
|
||||||
|
false -> View.VISIBLE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update list dates
|
||||||
|
recyclerView.adapter?.notifyDataSetChanged()
|
||||||
|
|
||||||
|
// Check for ongoing breastfeeding timer
|
||||||
|
restoreBreastfeedingTimerIfNeeded()
|
||||||
|
|
||||||
|
// Check for ongoing sleep timer
|
||||||
|
restoreSleepTimerIfNeeded()
|
||||||
|
|
||||||
|
if (logbook != null) {
|
||||||
|
// Already running: reload data for currently selected logbook
|
||||||
|
loadLogbook(logbook!!.name)
|
||||||
|
} else {
|
||||||
|
// First start: load logbook list
|
||||||
loadLogbookList()
|
loadLogbookList()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
handler.removeCallbacks(updateListRunnable)
|
handler.removeCallbacks(updateListRunnable)
|
||||||
|
|
||||||
|
// Clean up breastfeeding timer UI (state is preserved in SharedPreferences)
|
||||||
|
bfTimerRunnable?.let { bfTimerHandler?.removeCallbacks(it) }
|
||||||
|
bfTimerDialog?.dismiss()
|
||||||
|
|
||||||
|
// Clean up sleep timer UI (state is preserved in SharedPreferences)
|
||||||
|
sleepTimerRunnable?.let { sleepTimerHandler?.removeCallbacks(it) }
|
||||||
|
sleepTimerDialog?.dismiss()
|
||||||
|
|
||||||
super.onStop()
|
super.onStop()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,16 +312,31 @@ class MainActivity : AppCompatActivity() {
|
|||||||
alertDialog.show()
|
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) {
|
fun askNotes(lunaEvent: LunaEvent) {
|
||||||
val d = AlertDialog.Builder(this)
|
val d = AlertDialog.Builder(this)
|
||||||
val dialogView = layoutInflater.inflate(R.layout.dialog_notes, null)
|
val dialogView = layoutInflater.inflate(R.layout.dialog_notes, null)
|
||||||
d.setTitle(lunaEvent.getTypeDescription(this))
|
d.setTitle(lunaEvent.getTypeDescription(this))
|
||||||
d.setMessage(
|
d.setMessage(lunaEvent.getDialogMessage(this))
|
||||||
when (lunaEvent.type){
|
|
||||||
LunaEvent.TYPE_MEDICINE -> R.string.log_medicine_dialog_description
|
|
||||||
else -> R.string.log_notes_dialog_description
|
|
||||||
}
|
|
||||||
)
|
|
||||||
d.setView(dialogView)
|
d.setView(dialogView)
|
||||||
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
|
val notesET = dialogView.findViewById<EditText>(R.id.notes_edittext)
|
||||||
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
|
val qtyET = dialogView.findViewById<EditText>(R.id.notes_qty_edittext)
|
||||||
@@ -273,6 +361,230 @@ class MainActivity : AppCompatActivity() {
|
|||||||
alertDialog.show()
|
alertDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun startBreastfeedingTimer(eventType: String) {
|
||||||
|
// Check if timer already running
|
||||||
|
if (bfTimerType != null) {
|
||||||
|
Toast.makeText(this, R.string.breastfeeding_timer_already_running, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save timer state
|
||||||
|
bfTimerStartTime = System.currentTimeMillis()
|
||||||
|
bfTimerType = eventType
|
||||||
|
saveBreastfeedingTimerState()
|
||||||
|
|
||||||
|
// Show timer dialog
|
||||||
|
showBreastfeedingTimerDialog(eventType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showBreastfeedingTimerDialog(eventType: String) {
|
||||||
|
val d = AlertDialog.Builder(this)
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.breastfeeding_timer_dialog, null)
|
||||||
|
d.setTitle(R.string.breastfeeding_timer_title)
|
||||||
|
d.setView(dialogView)
|
||||||
|
d.setCancelable(false)
|
||||||
|
|
||||||
|
val timerDisplay = dialogView.findViewById<TextView>(R.id.breastfeeding_timer_display)
|
||||||
|
val sideEmoji = dialogView.findViewById<TextView>(R.id.breastfeeding_side_emoji)
|
||||||
|
sideEmoji.text = LunaEvent(eventType).getTypeEmoji(this)
|
||||||
|
|
||||||
|
// Set up timer updates
|
||||||
|
bfTimerHandler = Handler(mainLooper)
|
||||||
|
bfTimerRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
val elapsed = (System.currentTimeMillis() - bfTimerStartTime) / 1000
|
||||||
|
val minutes = elapsed / 60
|
||||||
|
val seconds = elapsed % 60
|
||||||
|
timerDisplay.text = String.format("%02d:%02d", minutes, seconds)
|
||||||
|
bfTimerHandler?.postDelayed(this, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bfTimerHandler?.post(bfTimerRunnable!!)
|
||||||
|
|
||||||
|
d.setPositiveButton(R.string.breastfeeding_timer_stop) { _, _ ->
|
||||||
|
stopBreastfeedingTimer()
|
||||||
|
}
|
||||||
|
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||||
|
cancelBreastfeedingTimer()
|
||||||
|
dialogInterface.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
bfTimerDialog = d.create()
|
||||||
|
bfTimerDialog?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopBreastfeedingTimer() {
|
||||||
|
bfTimerHandler?.removeCallbacks(bfTimerRunnable!!)
|
||||||
|
|
||||||
|
val durationMillis = System.currentTimeMillis() - bfTimerStartTime
|
||||||
|
val durationMinutes = Math.max(1, (durationMillis / 60000).toInt()) // Minimum 1 minute
|
||||||
|
|
||||||
|
val eventType = bfTimerType
|
||||||
|
clearBreastfeedingTimerState()
|
||||||
|
|
||||||
|
if (eventType != null) {
|
||||||
|
logEvent(LunaEvent(eventType, durationMinutes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelBreastfeedingTimer() {
|
||||||
|
bfTimerHandler?.removeCallbacks(bfTimerRunnable!!)
|
||||||
|
clearBreastfeedingTimerState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun askBreastfeedingDuration(eventType: String) {
|
||||||
|
val d = AlertDialog.Builder(this)
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.breastfeeding_duration_dialog, null)
|
||||||
|
d.setTitle(R.string.breastfeeding_duration_title)
|
||||||
|
d.setMessage(R.string.breastfeeding_duration_description)
|
||||||
|
d.setView(dialogView)
|
||||||
|
|
||||||
|
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.breastfeeding_duration_picker)
|
||||||
|
numberPicker.minValue = 1
|
||||||
|
numberPicker.maxValue = 60
|
||||||
|
numberPicker.value = 15 // Default 15 minutes
|
||||||
|
numberPicker.wrapSelectorWheel = false
|
||||||
|
|
||||||
|
d.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
logEvent(LunaEvent(eventType, numberPicker.value))
|
||||||
|
}
|
||||||
|
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||||
|
dialogInterface.dismiss()
|
||||||
|
}
|
||||||
|
d.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveBreastfeedingTimerState() {
|
||||||
|
LocalSettingsRepository(this).saveBreastfeedingTimer(bfTimerStartTime, bfTimerType ?: "")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearBreastfeedingTimerState() {
|
||||||
|
bfTimerStartTime = 0
|
||||||
|
bfTimerType = null
|
||||||
|
bfTimerDialog = null
|
||||||
|
LocalSettingsRepository(this).clearBreastfeedingTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreBreastfeedingTimerIfNeeded() {
|
||||||
|
val timerState = LocalSettingsRepository(this).loadBreastfeedingTimer()
|
||||||
|
if (timerState != null && timerState.first > 0 && timerState.second.isNotEmpty()) {
|
||||||
|
bfTimerStartTime = timerState.first
|
||||||
|
bfTimerType = timerState.second
|
||||||
|
showBreastfeedingTimerDialog(timerState.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep timer methods
|
||||||
|
fun startSleepTimer() {
|
||||||
|
// Check if timer already running
|
||||||
|
if (sleepTimerStartTime > 0) {
|
||||||
|
Toast.makeText(this, R.string.sleep_timer_already_running, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save timer state
|
||||||
|
sleepTimerStartTime = System.currentTimeMillis()
|
||||||
|
saveSleepTimerState()
|
||||||
|
|
||||||
|
// Show timer dialog
|
||||||
|
showSleepTimerDialog()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSleepTimerDialog() {
|
||||||
|
val d = AlertDialog.Builder(this)
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.sleep_timer_dialog, null)
|
||||||
|
d.setTitle(R.string.sleep_timer_title)
|
||||||
|
d.setView(dialogView)
|
||||||
|
d.setCancelable(false)
|
||||||
|
|
||||||
|
val timerDisplay = dialogView.findViewById<TextView>(R.id.sleep_timer_display)
|
||||||
|
|
||||||
|
// Set up timer updates
|
||||||
|
sleepTimerHandler = Handler(mainLooper)
|
||||||
|
sleepTimerRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
val elapsed = (System.currentTimeMillis() - sleepTimerStartTime) / 1000
|
||||||
|
val hours = elapsed / 3600
|
||||||
|
val minutes = (elapsed % 3600) / 60
|
||||||
|
val seconds = elapsed % 60
|
||||||
|
timerDisplay.text = if (hours > 0) {
|
||||||
|
String.format("%d:%02d:%02d", hours, minutes, seconds)
|
||||||
|
} else {
|
||||||
|
String.format("%02d:%02d", minutes, seconds)
|
||||||
|
}
|
||||||
|
sleepTimerHandler?.postDelayed(this, 1000)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sleepTimerHandler?.post(sleepTimerRunnable!!)
|
||||||
|
|
||||||
|
d.setPositiveButton(R.string.sleep_timer_stop) { _, _ ->
|
||||||
|
stopSleepTimer()
|
||||||
|
}
|
||||||
|
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||||
|
cancelSleepTimer()
|
||||||
|
dialogInterface.dismiss()
|
||||||
|
}
|
||||||
|
|
||||||
|
sleepTimerDialog = d.create()
|
||||||
|
sleepTimerDialog?.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stopSleepTimer() {
|
||||||
|
sleepTimerHandler?.removeCallbacks(sleepTimerRunnable!!)
|
||||||
|
|
||||||
|
val durationMillis = System.currentTimeMillis() - sleepTimerStartTime
|
||||||
|
val durationMinutes = Math.max(1, (durationMillis / 60000).toInt()) // Minimum 1 minute
|
||||||
|
|
||||||
|
clearSleepTimerState()
|
||||||
|
|
||||||
|
logEvent(LunaEvent(LunaEvent.TYPE_SLEEP, durationMinutes))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelSleepTimer() {
|
||||||
|
sleepTimerHandler?.removeCallbacks(sleepTimerRunnable!!)
|
||||||
|
clearSleepTimerState()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun askSleepDuration() {
|
||||||
|
val d = AlertDialog.Builder(this)
|
||||||
|
val dialogView = layoutInflater.inflate(R.layout.sleep_duration_dialog, null)
|
||||||
|
d.setTitle(R.string.sleep_duration_title)
|
||||||
|
d.setMessage(R.string.sleep_duration_description)
|
||||||
|
d.setView(dialogView)
|
||||||
|
|
||||||
|
val numberPicker = dialogView.findViewById<NumberPicker>(R.id.sleep_duration_picker)
|
||||||
|
numberPicker.minValue = 1
|
||||||
|
numberPicker.maxValue = 180 // Up to 3 hours
|
||||||
|
numberPicker.value = 30 // Default 30 minutes
|
||||||
|
numberPicker.wrapSelectorWheel = false
|
||||||
|
|
||||||
|
d.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
logEvent(LunaEvent(LunaEvent.TYPE_SLEEP, numberPicker.value))
|
||||||
|
}
|
||||||
|
d.setNegativeButton(android.R.string.cancel) { dialogInterface, _ ->
|
||||||
|
dialogInterface.dismiss()
|
||||||
|
}
|
||||||
|
d.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSleepTimerState() {
|
||||||
|
LocalSettingsRepository(this).saveSleepTimer(sleepTimerStartTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSleepTimerState() {
|
||||||
|
sleepTimerStartTime = 0
|
||||||
|
sleepTimerDialog = null
|
||||||
|
LocalSettingsRepository(this).clearSleepTimer()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun restoreSleepTimerIfNeeded() {
|
||||||
|
val startTime = LocalSettingsRepository(this).loadSleepTimer()
|
||||||
|
if (startTime > 0) {
|
||||||
|
sleepTimerStartTime = startTime
|
||||||
|
showSleepTimerDialog()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -283,7 +595,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
d.setPositiveButton(R.string.trim_logbook_dialog_button_ok) { dialogInterface, i ->
|
d.setPositiveButton(R.string.trim_logbook_dialog_button_ok) { dialogInterface, i ->
|
||||||
logbook.trim()
|
logbook?.trim()
|
||||||
saveLogbook()
|
saveLogbook()
|
||||||
}
|
}
|
||||||
d.setNegativeButton(R.string.trim_logbook_dialog_button_cancel) { dialogInterface, i ->
|
d.setNegativeButton(R.string.trim_logbook_dialog_button_cancel) { dialogInterface, i ->
|
||||||
@@ -293,22 +605,159 @@ class MainActivity : AppCompatActivity() {
|
|||||||
alertDialog.show()
|
alertDialog.show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showEventDetailDialog(event: LunaEvent) {
|
fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
|
||||||
val dateFormat = DateFormat.getDateTimeInstance();
|
var previousEvent: LunaEvent? = null
|
||||||
|
for (item in items) {
|
||||||
|
if (item.type == event.type && item.time < event.time) {
|
||||||
|
if (previousEvent == null) {
|
||||||
|
previousEvent = item
|
||||||
|
} else if (previousEvent.time < item.time) {
|
||||||
|
previousEvent = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return previousEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getNextSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
|
||||||
|
var nextEvent: LunaEvent? = null
|
||||||
|
for (item in items) {
|
||||||
|
if (item.type == event.type && item.time > event.time) {
|
||||||
|
if (nextEvent == null) {
|
||||||
|
nextEvent = item
|
||||||
|
} else if (nextEvent.time > item.time) {
|
||||||
|
nextEvent = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nextEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
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 d = AlertDialog.Builder(this)
|
val d = AlertDialog.Builder(this)
|
||||||
d.setTitle(R.string.dialog_event_detail_title)
|
d.setTitle(R.string.dialog_event_detail_title)
|
||||||
val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null)
|
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_emoji).text = 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_description).text = 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).text =
|
||||||
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity).setText(
|
|
||||||
NumericUtils(this).formatEventQuantity(event)
|
NumericUtils(this).formatEventQuantity(event)
|
||||||
|
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).text = event.notes
|
||||||
|
|
||||||
|
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), DateUtils.formatDateTime(event.time))
|
||||||
|
dateTextView.setOnClickListener {
|
||||||
|
// Show datetime picker
|
||||||
|
val startYear = currentDateTime.get(Calendar.YEAR)
|
||||||
|
val startMonth = currentDateTime.get(Calendar.MONTH)
|
||||||
|
val startDay = currentDateTime.get(Calendar.DAY_OF_MONTH)
|
||||||
|
val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY)
|
||||||
|
val startMinute = currentDateTime.get(Calendar.MINUTE)
|
||||||
|
|
||||||
|
DatePickerDialog(this, { _, year, month, day ->
|
||||||
|
TimePickerDialog(this, { _, hour, minute ->
|
||||||
|
val pickedDateTime = Calendar.getInstance()
|
||||||
|
pickedDateTime.set(year, month, day, hour, minute)
|
||||||
|
// Save event and move it to the right position in the logbook
|
||||||
|
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()
|
||||||
|
}, startHour, startMinute, android.text.format.DateFormat.is24HourFormat(this@MainActivity)).show()
|
||||||
|
}, startYear, startMonth, startDay).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make quantity editable for breastfeeding and sleep events
|
||||||
|
val quantityTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity)
|
||||||
|
val isBreastfeeding = event.type in listOf(
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE,
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE,
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE
|
||||||
)
|
)
|
||||||
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).setText(event.notes)
|
val isSleep = event.type == LunaEvent.TYPE_SLEEP
|
||||||
|
if ((isBreastfeeding || isSleep) && event.quantity > 0) {
|
||||||
|
quantityTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_edit, 0)
|
||||||
|
quantityTextView.compoundDrawableTintList = ColorStateList.valueOf(getColor(R.color.accent))
|
||||||
|
quantityTextView.setOnClickListener {
|
||||||
|
val pickerDialog = AlertDialog.Builder(this@MainActivity)
|
||||||
|
val pickerView = if (isSleep) {
|
||||||
|
layoutInflater.inflate(R.layout.sleep_duration_dialog, null)
|
||||||
|
} else {
|
||||||
|
layoutInflater.inflate(R.layout.breastfeeding_duration_dialog, null)
|
||||||
|
}
|
||||||
|
val picker = pickerView.findViewById<NumberPicker>(
|
||||||
|
if (isSleep) R.id.sleep_duration_picker else R.id.breastfeeding_duration_picker
|
||||||
|
)
|
||||||
|
picker.minValue = 1
|
||||||
|
picker.maxValue = if (isSleep) 180 else 60
|
||||||
|
picker.value = if (event.quantity > 0) Math.min(event.quantity, picker.maxValue) else if (isSleep) 30 else 15
|
||||||
|
|
||||||
|
pickerDialog.setTitle(if (isSleep) R.string.sleep_duration_title else R.string.breastfeeding_duration_title)
|
||||||
|
pickerDialog.setView(pickerView)
|
||||||
|
pickerDialog.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
event.quantity = picker.value
|
||||||
|
quantityTextView.text = NumericUtils(this@MainActivity).formatEventQuantity(event)
|
||||||
|
recyclerView.adapter?.notifyDataSetChanged()
|
||||||
|
saveLogbook()
|
||||||
|
}
|
||||||
|
pickerDialog.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
pickerDialog.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d.setView(dialogView)
|
d.setView(dialogView)
|
||||||
d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> dialogInterface.dismiss() }
|
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i -> dialogInterface.dismiss() }
|
||||||
|
d.setNeutralButton(R.string.dialog_event_detail_delete_button) { dialogInterface, i -> deleteEvent(event) }
|
||||||
val alertDialog = d.create()
|
val alertDialog = d.create()
|
||||||
alertDialog.show()
|
alertDialog.show()
|
||||||
|
alertDialog.getButton(DialogInterface.BUTTON_NEUTRAL).setTextColor(ContextCompat.getColor(this, R.color.danger))
|
||||||
|
alertDialog.setOnDismissListener({
|
||||||
|
// Resume logbook update
|
||||||
|
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)
|
||||||
|
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next)
|
||||||
|
val nextEvent = getNextSameEvent(event, items)
|
||||||
|
val previousEvent = getPreviousSameEvent(event, items)
|
||||||
|
|
||||||
|
if (previousEvent != null) {
|
||||||
|
val emoji = previousEvent.getTypeEmoji(applicationContext)
|
||||||
|
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
|
||||||
|
previousTextView.text = String.format("⬅️ %s %s", emoji, time)
|
||||||
|
previousTextView.setOnClickListener {
|
||||||
|
alertDialog.cancel()
|
||||||
|
showEventDetailDialog(previousEvent, items)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
previousTextView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nextEvent != null) {
|
||||||
|
val emoji = nextEvent.getTypeEmoji(applicationContext)
|
||||||
|
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
|
||||||
|
nextTextView.text = String.format("%s %s ➡️", time, emoji)
|
||||||
|
nextTextView.setOnClickListener {
|
||||||
|
alertDialog.cancel()
|
||||||
|
showEventDetailDialog(nextEvent, items)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
nextTextView.visibility = View.GONE
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAddLogbookDialog(requestedByUser: Boolean) {
|
fun showAddLogbookDialog(requestedByUser: Boolean) {
|
||||||
@@ -347,7 +796,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
sAdapter.setDropDownViewResource(R.layout.row_logbook_spinner)
|
sAdapter.setDropDownViewResource(R.layout.row_logbook_spinner)
|
||||||
for (ln in logbooksNames) {
|
for (ln in logbooksNames) {
|
||||||
sAdapter.add(
|
sAdapter.add(
|
||||||
if (ln.isEmpty()) getString(R.string.default_logbook_name) else ln
|
ln.ifEmpty { getString(R.string.default_logbook_name) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
spinner.adapter = sAdapter
|
spinner.adapter = sAdapter
|
||||||
@@ -359,28 +808,46 @@ class MainActivity : AppCompatActivity() {
|
|||||||
id: Long
|
id: Long
|
||||||
) {
|
) {
|
||||||
// Changed logbook: empty list
|
// Changed logbook: empty list
|
||||||
adapter.items.clear()
|
setListAdapter(arrayListOf())
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
// Load logbook
|
// Load logbook
|
||||||
loadLogbook(logbooksNames.get(position))
|
loadLogbook(logbooksNames.get(position))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIOError(error: IOException) {
|
override fun onIOError(error: IOException) {
|
||||||
TODO("Not yet implemented")
|
Log.e(TAG, "Unable to load logbooks list (IOError): $error")
|
||||||
|
runOnUiThread({
|
||||||
|
setLoading(false)
|
||||||
|
onRepoError(getString(R.string.settings_network_error) + error.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onWebDAVError(error: SardineException) {
|
override fun onWebDAVError(error: SardineException) {
|
||||||
TODO("Not yet implemented")
|
Log.e(TAG, "Unable to load logbooks list (SardineException): $error")
|
||||||
|
runOnUiThread({
|
||||||
|
setLoading(false)
|
||||||
|
onRepoError(
|
||||||
|
if(error.toString().contains("401")) {
|
||||||
|
getString(R.string.settings_webdav_error_denied)
|
||||||
|
} else if(error.toString().contains("503")) {
|
||||||
|
getString(R.string.settings_webdav_error_server_offline)
|
||||||
|
} else {
|
||||||
|
getString(R.string.settings_webdav_error_generic) + error.toString()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onError(error: Exception) {
|
override fun onError(error: Exception) {
|
||||||
TODO("Not yet implemented")
|
Log.e(TAG, "Unable to load logbooks list: $error")
|
||||||
|
runOnUiThread({
|
||||||
|
setLoading(false)
|
||||||
|
onRepoError(getString(R.string.settings_generic_error) + error.toString())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -409,6 +876,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onRepoError(
|
onRepoError(
|
||||||
if(error.toString().contains("401")) {
|
if(error.toString().contains("401")) {
|
||||||
getString(R.string.settings_webdav_error_denied)
|
getString(R.string.settings_webdav_error_denied)
|
||||||
|
} else if(error.toString().contains("503")) {
|
||||||
|
getString(R.string.settings_webdav_error_server_offline)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.settings_webdav_error_generic) + error.toString()
|
getString(R.string.settings_webdav_error_generic) + error.toString()
|
||||||
}
|
}
|
||||||
@@ -449,7 +918,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
showLogbook()
|
showLogbook()
|
||||||
|
|
||||||
if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) {
|
if (DEBUG_CHECK_LOGBOOK_CONSISTENCY) {
|
||||||
for (e in logbook.logs) {
|
for (e in logbook?.logs ?: listOf()) {
|
||||||
val em = e.getTypeEmoji(this@MainActivity)
|
val em = e.getTypeEmoji(this@MainActivity)
|
||||||
if (em == getString(R.string.event_unknown_type)) {
|
if (em == getString(R.string.event_unknown_type)) {
|
||||||
Log.e(TAG, "UNKNOWN: ${e.type}")
|
Log.e(TAG, "UNKNOWN: ${e.type}")
|
||||||
@@ -472,6 +941,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onRepoError(
|
onRepoError(
|
||||||
if(error.toString().contains("401")) {
|
if(error.toString().contains("401")) {
|
||||||
getString(R.string.settings_webdav_error_denied)
|
getString(R.string.settings_webdav_error_denied)
|
||||||
|
} else if(error.toString().contains("503")) {
|
||||||
|
getString(R.string.settings_webdav_error_server_offline)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.settings_webdav_error_generic) + error.toString()
|
getString(R.string.settings_webdav_error_generic) + error.toString()
|
||||||
}
|
}
|
||||||
@@ -492,7 +963,6 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onRepoError(getString(R.string.settings_generic_error) + error.toString())
|
onRepoError(getString(R.string.settings_generic_error) + error.toString())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,26 +976,42 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
fun logEvent(event: LunaEvent) {
|
fun logEvent(event: LunaEvent) {
|
||||||
savingEvent(true)
|
savingEvent(true)
|
||||||
adapter.items.add(0, event)
|
|
||||||
adapter.notifyItemInserted(0)
|
event.signature = signature
|
||||||
recyclerView.smoothScrollToPosition(0)
|
|
||||||
|
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
logbook.logs.add(0, event)
|
logbook?.logs?.add(0, event)
|
||||||
|
recyclerView.adapter?.notifyItemInserted(0)
|
||||||
|
recyclerView.smoothScrollToPosition(0)
|
||||||
saveLogbook(event)
|
saveLogbook(event)
|
||||||
|
|
||||||
// Check logbook size to avoid OOM errors
|
// Check logbook size to avoid OOM errors
|
||||||
if (logbook.isTooBig()) {
|
if (logbook?.isTooBig() == true) {
|
||||||
askToTrimLogbook()
|
askToTrimLogbook()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteEvent(event: LunaEvent) {
|
||||||
|
// Update view
|
||||||
|
savingEvent(true)
|
||||||
|
|
||||||
|
// Update data
|
||||||
|
setLoading(true)
|
||||||
|
logbook?.logs?.remove(event)
|
||||||
|
recyclerView.adapter?.notifyDataSetChanged()
|
||||||
|
saveLogbook()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the logbook. If saving while adding an event, please specify the event so in case
|
* Saves the logbook. If saving while adding an event, please specify the event so in case
|
||||||
* of error can be removed from the list.
|
* of error can be removed from the list.
|
||||||
*/
|
*/
|
||||||
fun saveLogbook(lastEventAdded: LunaEvent? = null) {
|
fun saveLogbook(lastEventAdded: LunaEvent? = null) {
|
||||||
logbookRepo?.saveLogbook(this, logbook, object: LogbookSavedListener{
|
if (logbook == null) {
|
||||||
|
Log.e(TAG, "Trying to save logbook, but logbook is null!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logbookRepo?.saveLogbook(this, logbook!!, object: LogbookSavedListener{
|
||||||
override fun onLogbookSaved() {
|
override fun onLogbookSaved() {
|
||||||
Log.d(TAG, "Logbook saved")
|
Log.d(TAG, "Logbook saved")
|
||||||
runOnUiThread({
|
runOnUiThread({
|
||||||
@@ -558,6 +1044,8 @@ class MainActivity : AppCompatActivity() {
|
|||||||
onRepoError(
|
onRepoError(
|
||||||
if(error.toString().contains("401")) {
|
if(error.toString().contains("401")) {
|
||||||
getString(R.string.settings_webdav_error_denied)
|
getString(R.string.settings_webdav_error_denied)
|
||||||
|
} else if(error.toString().contains("503")) {
|
||||||
|
getString(R.string.settings_webdav_error_server_offline)
|
||||||
} else {
|
} else {
|
||||||
getString(R.string.settings_webdav_error_generic) + error.toString()
|
getString(R.string.settings_webdav_error_generic) + error.toString()
|
||||||
}
|
}
|
||||||
@@ -593,8 +1081,7 @@ class MainActivity : AppCompatActivity() {
|
|||||||
setLoading(false)
|
setLoading(false)
|
||||||
|
|
||||||
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this@MainActivity, R.string.toast_event_add_error, Toast.LENGTH_SHORT).show()
|
||||||
adapter.items.remove(event)
|
recyclerView.adapter?.notifyDataSetChanged()
|
||||||
adapter.notifyDataSetChanged()
|
|
||||||
savingEvent(false)
|
savingEvent(false)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -625,10 +1112,19 @@ class MainActivity : AppCompatActivity() {
|
|||||||
isOutsideTouchable = true
|
isOutsideTouchable = true
|
||||||
val inflater = LayoutInflater.from(anchor.context)
|
val inflater = LayoutInflater.from(anchor.context)
|
||||||
contentView = inflater.inflate(R.layout.more_events_popup, null)
|
contentView = inflater.inflate(R.layout.more_events_popup, null)
|
||||||
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener({
|
contentView.findViewById<View>(R.id.button_sleep).setOnClickListener {
|
||||||
|
startSleepTimer()
|
||||||
|
dismiss()
|
||||||
|
}
|
||||||
|
contentView.findViewById<View>(R.id.button_sleep).setOnLongClickListener {
|
||||||
|
askSleepDuration()
|
||||||
|
dismiss()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
|
||||||
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE))
|
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE))
|
||||||
dismiss()
|
dismiss()
|
||||||
})
|
}
|
||||||
contentView.findViewById<View>(R.id.button_enema).setOnClickListener({
|
contentView.findViewById<View>(R.id.button_enema).setOnClickListener({
|
||||||
logEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
|
logEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
|
||||||
dismiss()
|
dismiss()
|
||||||
@@ -641,12 +1137,26 @@ class MainActivity : AppCompatActivity() {
|
|||||||
askTemperatureValue()
|
askTemperatureValue()
|
||||||
dismiss()
|
dismiss()
|
||||||
})
|
})
|
||||||
|
contentView.findViewById<View>(R.id.button_puke).setOnClickListener({
|
||||||
|
askPukeValue()
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({
|
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({
|
||||||
logEvent(
|
logEvent(
|
||||||
LunaEvent(LunaEvent.TYPE_COLIC)
|
LunaEvent(LunaEvent.TYPE_COLIC)
|
||||||
)
|
)
|
||||||
dismiss()
|
dismiss()
|
||||||
})
|
})
|
||||||
|
contentView.findViewById<View>(R.id.button_scale).setOnClickListener({
|
||||||
|
askWeightValue()
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
|
contentView.findViewById<View>(R.id.button_bath).setOnClickListener({
|
||||||
|
logEvent(
|
||||||
|
LunaEvent(LunaEvent.TYPE_BATH)
|
||||||
|
)
|
||||||
|
dismiss()
|
||||||
|
})
|
||||||
}.also { popupWindow ->
|
}.also { popupWindow ->
|
||||||
popupWindow.setOnDismissListener({
|
popupWindow.setOnDismissListener({
|
||||||
Handler(mainLooper).postDelayed({
|
Handler(mainLooper).postDelayed({
|
||||||
|
|||||||
@@ -1,20 +1,26 @@
|
|||||||
package it.danieleverducci.lunatracker
|
package it.danieleverducci.lunatracker
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||||
|
import it.danieleverducci.lunatracker.entities.Logbook
|
||||||
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
|
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
|
||||||
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
|
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
|
||||||
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
|
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
|
||||||
import it.danieleverducci.lunatracker.repository.LogbookRepository
|
|
||||||
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
|
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
|
||||||
import okio.IOException
|
import okio.IOException
|
||||||
import org.json.JSONException
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
open class SettingsActivity : AppCompatActivity() {
|
open class SettingsActivity : AppCompatActivity() {
|
||||||
protected lateinit var settingsRepository: LocalSettingsRepository
|
protected lateinit var settingsRepository: LocalSettingsRepository
|
||||||
@@ -24,6 +30,17 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
protected lateinit var textViewWebDAVUser: TextView
|
protected lateinit var textViewWebDAVUser: TextView
|
||||||
protected lateinit var textViewWebDAVPass: TextView
|
protected lateinit var textViewWebDAVPass: TextView
|
||||||
protected lateinit var progressIndicator: LinearProgressIndicator
|
protected lateinit var progressIndicator: LinearProgressIndicator
|
||||||
|
protected lateinit var switchNoBreastfeeding: SwitchMaterial
|
||||||
|
protected lateinit var textViewSignature: EditText
|
||||||
|
|
||||||
|
// Activity Result Launchers for Export/Import
|
||||||
|
private val exportLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.CreateDocument("application/json")
|
||||||
|
) { uri -> uri?.let { exportLogbookToUri(it) } }
|
||||||
|
|
||||||
|
private val importLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.OpenDocument()
|
||||||
|
) { uri -> uri?.let { importLogbookFromUri(it) } }
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
@@ -35,12 +52,21 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
|
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
|
||||||
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
|
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
|
||||||
progressIndicator = findViewById(R.id.progress_indicator)
|
progressIndicator = findViewById(R.id.progress_indicator)
|
||||||
|
switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
|
||||||
|
textViewSignature = findViewById(R.id.settings_signature)
|
||||||
|
|
||||||
findViewById<View>(R.id.settings_save).setOnClickListener({
|
findViewById<View>(R.id.settings_save).setOnClickListener({
|
||||||
validateAndSave()
|
validateAndSave()
|
||||||
})
|
})
|
||||||
findViewById<View>(R.id.settings_cancel).setOnClickListener({
|
findViewById<View>(R.id.settings_cancel).setOnClickListener({
|
||||||
finish()
|
finish()
|
||||||
})
|
})
|
||||||
|
findViewById<View>(R.id.settings_export).setOnClickListener({
|
||||||
|
startExport()
|
||||||
|
})
|
||||||
|
findViewById<View>(R.id.settings_import).setOnClickListener({
|
||||||
|
startImport()
|
||||||
|
})
|
||||||
|
|
||||||
settingsRepository = LocalSettingsRepository(this)
|
settingsRepository = LocalSettingsRepository(this)
|
||||||
loadSettings()
|
loadSettings()
|
||||||
@@ -49,15 +75,21 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
fun loadSettings() {
|
fun loadSettings() {
|
||||||
val dataRepo = settingsRepository.loadDataRepository()
|
val dataRepo = settingsRepository.loadDataRepository()
|
||||||
val webDavCredentials = settingsRepository.loadWebdavCredentials()
|
val webDavCredentials = settingsRepository.loadWebdavCredentials()
|
||||||
|
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
|
||||||
|
val signature = settingsRepository.loadSignature()
|
||||||
|
|
||||||
when (dataRepo) {
|
when (dataRepo) {
|
||||||
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
|
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
|
||||||
LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
|
LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
textViewSignature.setText(signature)
|
||||||
|
switchNoBreastfeeding.isChecked = noBreastfeeding
|
||||||
|
|
||||||
if (webDavCredentials != null) {
|
if (webDavCredentials != null) {
|
||||||
textViewWebDAVUrl.setText(webDavCredentials[0])
|
textViewWebDAVUrl.text = webDavCredentials[0]
|
||||||
textViewWebDAVUser.setText(webDavCredentials[1])
|
textViewWebDAVUser.text = webDavCredentials[1]
|
||||||
textViewWebDAVPass.setText(webDavCredentials[2])
|
textViewWebDAVPass.text = webDavCredentials[2]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +181,8 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
|
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
|
||||||
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
|
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
|
||||||
)
|
)
|
||||||
|
settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
|
||||||
|
settingsRepository.saveSignature(textViewSignature.text.toString())
|
||||||
settingsRepository.saveWebdavCredentials(
|
settingsRepository.saveWebdavCredentials(
|
||||||
textViewWebDAVUrl.text.toString(),
|
textViewWebDAVUrl.text.toString(),
|
||||||
textViewWebDAVUser.text.toString(),
|
textViewWebDAVUser.text.toString(),
|
||||||
@@ -163,7 +197,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
*/
|
*/
|
||||||
private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
|
private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
|
||||||
Thread(Runnable {
|
Thread(Runnable {
|
||||||
var errors = StringBuilder()
|
val errors = StringBuilder()
|
||||||
val fileLogbookRepo = FileLogbookRepository()
|
val fileLogbookRepo = FileLogbookRepository()
|
||||||
val logbooks = fileLogbookRepo.getAllLogbooks(this)
|
val logbooks = fileLogbookRepo.getAllLogbooks(this)
|
||||||
for (logbook in logbooks) {
|
for (logbook in logbooks) {
|
||||||
@@ -185,4 +219,136 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
fun onCopyLocalLogbooksToWebdavFinished(errors: String?)
|
fun onCopyLocalLogbooksToWebdavFinished(errors: String?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export/Import functionality
|
||||||
|
private fun startExport() {
|
||||||
|
val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US)
|
||||||
|
.format(java.util.Date())
|
||||||
|
exportLauncher.launch("lunatracker_backup_$timestamp.json")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun startImport() {
|
||||||
|
importLauncher.launch(arrayOf("application/json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exportLogbookToUri(uri: Uri) {
|
||||||
|
progressIndicator.visibility = View.VISIBLE
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
val fileLogbookRepo = FileLogbookRepository()
|
||||||
|
val logbooks = fileLogbookRepo.getAllLogbooks(this)
|
||||||
|
|
||||||
|
val json = JSONObject().apply {
|
||||||
|
put("version", 1)
|
||||||
|
put("app", "LunaTracker")
|
||||||
|
put("exported_at", System.currentTimeMillis())
|
||||||
|
put("logbooks", JSONArray().apply {
|
||||||
|
logbooks.forEach { logbook ->
|
||||||
|
put(JSONObject().apply {
|
||||||
|
put("name", logbook.name)
|
||||||
|
put("events", JSONArray().apply {
|
||||||
|
logbook.logs.forEach { event ->
|
||||||
|
put(event.toJson())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||||
|
outputStream.write(json.toString(2).toByteArray(Charsets.UTF_8))
|
||||||
|
}
|
||||||
|
|
||||||
|
val eventCount = logbooks.sumOf { it.logs.size }
|
||||||
|
runOnUiThread {
|
||||||
|
progressIndicator.visibility = View.INVISIBLE
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(R.string.export_success, eventCount),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
runOnUiThread {
|
||||||
|
progressIndicator.visibility = View.INVISIBLE
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(R.string.export_error) + e.message,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun importLogbookFromUri(uri: Uri) {
|
||||||
|
progressIndicator.visibility = View.VISIBLE
|
||||||
|
Thread {
|
||||||
|
try {
|
||||||
|
val jsonString = contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
|
||||||
|
?: throw Exception("Could not read file")
|
||||||
|
|
||||||
|
val json = JSONObject(jsonString)
|
||||||
|
val version = json.optInt("version", 1)
|
||||||
|
|
||||||
|
val fileLogbookRepo = FileLogbookRepository()
|
||||||
|
var totalEvents = 0
|
||||||
|
|
||||||
|
if (json.has("logbooks")) {
|
||||||
|
// New format with multiple logbooks
|
||||||
|
val logbooksArray = json.getJSONArray("logbooks")
|
||||||
|
for (i in 0 until logbooksArray.length()) {
|
||||||
|
val logbookJson = logbooksArray.getJSONObject(i)
|
||||||
|
val name = logbookJson.optString("name", "")
|
||||||
|
val eventsArray = logbookJson.getJSONArray("events")
|
||||||
|
|
||||||
|
val logbook = Logbook(name)
|
||||||
|
for (j in 0 until eventsArray.length()) {
|
||||||
|
try {
|
||||||
|
logbook.logs.add(LunaEvent(eventsArray.getJSONObject(j)))
|
||||||
|
totalEvents++
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// Skip invalid events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileLogbookRepo.saveLogbook(this, logbook)
|
||||||
|
}
|
||||||
|
} else if (json.has("events")) {
|
||||||
|
// Old format with single logbook
|
||||||
|
val name = json.optString("logbook_name", "")
|
||||||
|
val eventsArray = json.getJSONArray("events")
|
||||||
|
|
||||||
|
val logbook = Logbook(name)
|
||||||
|
for (i in 0 until eventsArray.length()) {
|
||||||
|
try {
|
||||||
|
logbook.logs.add(LunaEvent(eventsArray.getJSONObject(i)))
|
||||||
|
totalEvents++
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
// Skip invalid events
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fileLogbookRepo.saveLogbook(this, logbook)
|
||||||
|
}
|
||||||
|
|
||||||
|
runOnUiThread {
|
||||||
|
progressIndicator.visibility = View.INVISIBLE
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(R.string.import_success, totalEvents),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
runOnUiThread {
|
||||||
|
progressIndicator.visibility = View.INVISIBLE
|
||||||
|
Toast.makeText(
|
||||||
|
this,
|
||||||
|
getString(R.string.import_error) + ": " + e.message,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,207 @@
|
|||||||
|
package it.danieleverducci.lunatracker
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||||
|
import androidx.viewpager2.widget.ViewPager2
|
||||||
|
import com.google.android.material.tabs.TabLayout
|
||||||
|
import com.google.android.material.tabs.TabLayoutMediator
|
||||||
|
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||||
|
import it.danieleverducci.lunatracker.entities.Logbook
|
||||||
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
|
import it.danieleverducci.lunatracker.fragments.DailySummaryFragment
|
||||||
|
import it.danieleverducci.lunatracker.fragments.DiaperStatsFragment
|
||||||
|
import it.danieleverducci.lunatracker.fragments.FeedingStatsFragment
|
||||||
|
import it.danieleverducci.lunatracker.fragments.GrowthStatsFragment
|
||||||
|
import it.danieleverducci.lunatracker.fragments.SleepStatsFragment
|
||||||
|
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
|
||||||
|
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
|
||||||
|
import it.danieleverducci.lunatracker.repository.LogbookLoadedListener
|
||||||
|
import it.danieleverducci.lunatracker.repository.LogbookRepository
|
||||||
|
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
|
||||||
|
import okio.IOException
|
||||||
|
import org.json.JSONException
|
||||||
|
import utils.StatisticsCalculator
|
||||||
|
|
||||||
|
class StatisticsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "StatisticsActivity"
|
||||||
|
const val EXTRA_LOGBOOK_NAME = "logbook_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var viewPager: ViewPager2
|
||||||
|
private lateinit var tabLayout: TabLayout
|
||||||
|
private lateinit var periodSpinner: Spinner
|
||||||
|
|
||||||
|
private var events: List<LunaEvent> = emptyList()
|
||||||
|
private var selectedPeriod: Int = 7 // Default 7 days
|
||||||
|
private var logbookName: String = ""
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_statistics)
|
||||||
|
|
||||||
|
// Back button
|
||||||
|
findViewById<ImageView>(R.id.button_back).setOnClickListener {
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Title with logbook name
|
||||||
|
logbookName = intent.getStringExtra(EXTRA_LOGBOOK_NAME) ?: ""
|
||||||
|
if (logbookName.isNotEmpty()) {
|
||||||
|
findViewById<TextView>(R.id.statistics_title).text =
|
||||||
|
"${getString(R.string.statistics_title)} - $logbookName"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Period spinner
|
||||||
|
periodSpinner = findViewById(R.id.period_spinner)
|
||||||
|
val periods = arrayOf(
|
||||||
|
getString(R.string.stats_period_7days),
|
||||||
|
getString(R.string.stats_period_14days),
|
||||||
|
getString(R.string.stats_period_30days)
|
||||||
|
)
|
||||||
|
val periodAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, periods)
|
||||||
|
periodAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||||
|
periodSpinner.adapter = periodAdapter
|
||||||
|
periodSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||||
|
selectedPeriod = when (position) {
|
||||||
|
0 -> 7
|
||||||
|
1 -> 14
|
||||||
|
2 -> 30
|
||||||
|
else -> 7
|
||||||
|
}
|
||||||
|
notifyFragmentsOfPeriodChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ViewPager and TabLayout
|
||||||
|
viewPager = findViewById(R.id.view_pager)
|
||||||
|
tabLayout = findViewById(R.id.tab_layout)
|
||||||
|
|
||||||
|
// Load events
|
||||||
|
loadEvents()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadEvents() {
|
||||||
|
val settingsRepo = LocalSettingsRepository(this)
|
||||||
|
val repository: LogbookRepository = when (settingsRepo.loadDataRepository()) {
|
||||||
|
LocalSettingsRepository.DATA_REPO.WEBDAV -> {
|
||||||
|
val credentials = settingsRepo.loadWebdavCredentials()
|
||||||
|
if (credentials != null) {
|
||||||
|
WebDAVLogbookRepository(credentials[0], credentials[1], credentials[2])
|
||||||
|
} else {
|
||||||
|
FileLogbookRepository()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> FileLogbookRepository()
|
||||||
|
}
|
||||||
|
|
||||||
|
repository.loadLogbook(this, logbookName, object : LogbookLoadedListener {
|
||||||
|
override fun onLogbookLoaded(logbook: Logbook) {
|
||||||
|
runOnUiThread {
|
||||||
|
events = logbook.logs
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onIOError(error: IOException) {
|
||||||
|
Log.e(TAG, "IO error loading logbook", error)
|
||||||
|
runOnUiThread {
|
||||||
|
events = emptyList()
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onWebDAVError(error: SardineException) {
|
||||||
|
Log.e(TAG, "WebDAV error loading logbook", error)
|
||||||
|
runOnUiThread {
|
||||||
|
events = emptyList()
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onJSONError(error: JSONException) {
|
||||||
|
Log.e(TAG, "JSON error loading logbook", error)
|
||||||
|
runOnUiThread {
|
||||||
|
events = emptyList()
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(error: Exception) {
|
||||||
|
Log.e(TAG, "Error loading logbook", error)
|
||||||
|
runOnUiThread {
|
||||||
|
events = emptyList()
|
||||||
|
setupViewPager()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupViewPager() {
|
||||||
|
val adapter = StatisticsPagerAdapter(this)
|
||||||
|
viewPager.adapter = adapter
|
||||||
|
|
||||||
|
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||||
|
tab.text = when (position) {
|
||||||
|
0 -> getString(R.string.stats_tab_today)
|
||||||
|
1 -> getString(R.string.stats_tab_feeding)
|
||||||
|
2 -> getString(R.string.stats_tab_diapers)
|
||||||
|
3 -> getString(R.string.stats_tab_sleep)
|
||||||
|
4 -> getString(R.string.stats_tab_growth)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun notifyFragmentsOfPeriodChange() {
|
||||||
|
if (events.isEmpty()) return
|
||||||
|
|
||||||
|
// Force fragment refresh by recreating adapter
|
||||||
|
val currentItem = viewPager.currentItem
|
||||||
|
viewPager.adapter = StatisticsPagerAdapter(this)
|
||||||
|
viewPager.setCurrentItem(currentItem, false)
|
||||||
|
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||||
|
tab.text = when (position) {
|
||||||
|
0 -> getString(R.string.stats_tab_today)
|
||||||
|
1 -> getString(R.string.stats_tab_feeding)
|
||||||
|
2 -> getString(R.string.stats_tab_diapers)
|
||||||
|
3 -> getString(R.string.stats_tab_sleep)
|
||||||
|
4 -> getString(R.string.stats_tab_growth)
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
}.attach()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getEvents(): List<LunaEvent> = events
|
||||||
|
fun getSelectedPeriod(): Int = selectedPeriod
|
||||||
|
fun getCalculator(): StatisticsCalculator = StatisticsCalculator(events)
|
||||||
|
|
||||||
|
private inner class StatisticsPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||||
|
override fun getItemCount(): Int = 5
|
||||||
|
|
||||||
|
override fun createFragment(position: Int): Fragment {
|
||||||
|
return when (position) {
|
||||||
|
0 -> DailySummaryFragment()
|
||||||
|
1 -> FeedingStatsFragment()
|
||||||
|
2 -> DiaperStatsFragment()
|
||||||
|
3 -> SleepStatsFragment()
|
||||||
|
4 -> GrowthStatsFragment()
|
||||||
|
else -> DailySummaryFragment()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package it.danieleverducci.lunatracker.adapters
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.Paint
|
||||||
|
import android.graphics.Rect
|
||||||
|
import android.text.format.DateFormat
|
||||||
|
import android.view.View
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
class DaySeparatorDecoration(
|
||||||
|
private val context: Context,
|
||||||
|
private val items: List<LunaEvent>
|
||||||
|
) : RecyclerView.ItemDecoration() {
|
||||||
|
|
||||||
|
private val textPaint = Paint().apply {
|
||||||
|
color = context.getColor(R.color.grey)
|
||||||
|
textSize = 32f
|
||||||
|
textAlign = Paint.Align.CENTER
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val linePaint = Paint().apply {
|
||||||
|
color = context.getColor(R.color.grey)
|
||||||
|
strokeWidth = 1f
|
||||||
|
isAntiAlias = true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
val position = parent.getChildAdapterPosition(view)
|
||||||
|
if (shouldShowHeader(position)) {
|
||||||
|
outRect.top = 48
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDraw(canvas: Canvas, parent: RecyclerView, state: RecyclerView.State) {
|
||||||
|
for (i in 0 until parent.childCount) {
|
||||||
|
val child = parent.getChildAt(i)
|
||||||
|
val position = parent.getChildAdapterPosition(child)
|
||||||
|
|
||||||
|
if (shouldShowHeader(position)) {
|
||||||
|
val dateText = formatDate(items[position].time)
|
||||||
|
val y = child.top - 16f
|
||||||
|
|
||||||
|
// Linie links
|
||||||
|
canvas.drawLine(20f, y, parent.width / 2f - 80f, y, linePaint)
|
||||||
|
// Datum in der Mitte
|
||||||
|
canvas.drawText(dateText, parent.width / 2f, y + 10f, textPaint)
|
||||||
|
// Linie rechts
|
||||||
|
canvas.drawLine(parent.width / 2f + 80f, y, parent.width - 20f, y, linePaint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldShowHeader(position: Int): Boolean {
|
||||||
|
if (position <= 0 || position >= items.size) return false
|
||||||
|
|
||||||
|
val currentDay = getDay(items[position].time)
|
||||||
|
val previousDay = getDay(items[position - 1].time)
|
||||||
|
return currentDay != previousDay
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDay(timestamp: Long): Long {
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
cal.timeInMillis = timestamp * 1000
|
||||||
|
cal.set(Calendar.HOUR_OF_DAY, 0)
|
||||||
|
cal.set(Calendar.MINUTE, 0)
|
||||||
|
cal.set(Calendar.SECOND, 0)
|
||||||
|
cal.set(Calendar.MILLISECOND, 0)
|
||||||
|
return cal.timeInMillis
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun formatDate(timestamp: Long): String {
|
||||||
|
return DateFormat.getDateFormat(context).format(Date(timestamp * 1000))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,13 +14,14 @@ 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>()
|
private val items: ArrayList<LunaEvent>
|
||||||
val numericUtils: NumericUtils
|
val numericUtils: NumericUtils
|
||||||
var onItemClickListener: OnItemClickListener? = null
|
var onItemClickListener: OnItemClickListener? = null
|
||||||
val layoutRes: Int
|
val layoutRes: Int
|
||||||
|
|
||||||
constructor(context: Context) {
|
constructor(context: Context, items: ArrayList<LunaEvent>) {
|
||||||
this.context = context
|
this.context = context
|
||||||
|
this.items = items
|
||||||
this.numericUtils = NumericUtils(context)
|
this.numericUtils = NumericUtils(context)
|
||||||
|
|
||||||
val fontScale = context.resources.configuration.fontScale
|
val fontScale = context.resources.configuration.fontScale
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package it.danieleverducci.lunatracker.entities
|
|||||||
|
|
||||||
class Logbook(val name: String) {
|
class Logbook(val name: String) {
|
||||||
companion object {
|
companion object {
|
||||||
val MAX_SAFE_LOGBOOK_SIZE = 30000
|
const val MAX_SAFE_LOGBOOK_SIZE = 30000
|
||||||
}
|
}
|
||||||
val logs = ArrayList<LunaEvent>()
|
val logs = ArrayList<LunaEvent>()
|
||||||
|
|
||||||
@@ -16,4 +16,8 @@ class Logbook(val name: String) {
|
|||||||
fun trim() {
|
fun trim() {
|
||||||
logs.subList(MAX_SAFE_LOGBOOK_SIZE/2, logs.size).clear()
|
logs.subList(MAX_SAFE_LOGBOOK_SIZE/2, logs.size).clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun sort() {
|
||||||
|
logs.sortDescending()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -11,22 +11,26 @@ import java.util.Date
|
|||||||
* allow expandability and backwards compatibility (if a field is added in a
|
* allow expandability and backwards compatibility (if a field is added in a
|
||||||
* release, it is simply ignored by previous ones).
|
* release, it is simply ignored by previous ones).
|
||||||
*/
|
*/
|
||||||
class LunaEvent {
|
class LunaEvent: Comparable<LunaEvent> {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
|
const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
|
||||||
val TYPE_WEIGHT = "WEIGHT"
|
const val TYPE_WEIGHT = "WEIGHT"
|
||||||
val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
|
const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
|
||||||
val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
|
const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
|
||||||
val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
|
const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
|
||||||
val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
|
const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
|
||||||
val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
|
const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
|
||||||
val TYPE_MEDICINE = "MEDICINE"
|
const val TYPE_MEDICINE = "MEDICINE"
|
||||||
val TYPE_ENEMA = "ENEMA"
|
const val TYPE_ENEMA = "ENEMA"
|
||||||
val TYPE_NOTE = "NOTE"
|
const val TYPE_NOTE = "NOTE"
|
||||||
val TYPE_CUSTOM = "CUSTOM"
|
const val TYPE_CUSTOM = "CUSTOM"
|
||||||
val TYPE_COLIC = "COLIC"
|
const val TYPE_COLIC = "COLIC"
|
||||||
val TYPE_TEMPERATURE = "TEMPERATURE"
|
const val TYPE_TEMPERATURE = "TEMPERATURE"
|
||||||
|
const val TYPE_FOOD = "FOOD"
|
||||||
|
const val TYPE_PUKE = "PUKE"
|
||||||
|
const val TYPE_BATH = "BATH"
|
||||||
|
const val TYPE_SLEEP = "SLEEP"
|
||||||
}
|
}
|
||||||
|
|
||||||
private val jo: JSONObject
|
private val jo: JSONObject
|
||||||
@@ -52,6 +56,12 @@ class LunaEvent {
|
|||||||
set(value) {
|
set(value) {
|
||||||
jo.put("notes", 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) {
|
constructor(jo: JSONObject) {
|
||||||
this.jo = jo
|
this.jo = jo
|
||||||
@@ -88,6 +98,10 @@ class LunaEvent {
|
|||||||
TYPE_NOTE -> R.string.event_note_type
|
TYPE_NOTE -> R.string.event_note_type
|
||||||
TYPE_TEMPERATURE -> R.string.event_temperature_type
|
TYPE_TEMPERATURE -> R.string.event_temperature_type
|
||||||
TYPE_COLIC -> R.string.event_colic_type
|
TYPE_COLIC -> R.string.event_colic_type
|
||||||
|
TYPE_FOOD -> R.string.event_food_type
|
||||||
|
TYPE_PUKE -> R.string.event_puke_type
|
||||||
|
TYPE_BATH -> R.string.event_bath_type
|
||||||
|
TYPE_SLEEP -> R.string.event_sleep_type
|
||||||
else -> R.string.event_unknown_type
|
else -> R.string.event_unknown_type
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -108,16 +122,31 @@ class LunaEvent {
|
|||||||
TYPE_NOTE -> R.string.event_note_desc
|
TYPE_NOTE -> R.string.event_note_desc
|
||||||
TYPE_TEMPERATURE -> R.string.event_temperature_desc
|
TYPE_TEMPERATURE -> R.string.event_temperature_desc
|
||||||
TYPE_COLIC -> R.string.event_colic_desc
|
TYPE_COLIC -> R.string.event_colic_desc
|
||||||
|
TYPE_FOOD -> R.string.event_food_desc
|
||||||
|
TYPE_PUKE -> R.string.event_puke_desc
|
||||||
|
TYPE_BATH -> R.string.event_bath_desc
|
||||||
|
TYPE_SLEEP -> R.string.event_sleep_desc
|
||||||
else -> R.string.event_unknown_desc
|
else -> R.string.event_unknown_desc
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getDialogMessage(context: Context): String? {
|
||||||
|
return when(type) {
|
||||||
|
TYPE_MEDICINE -> context.getString(R.string.log_medicine_dialog_description)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun toJson(): JSONObject {
|
fun toJson(): JSONObject {
|
||||||
return jo
|
return jo
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
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 {
|
||||||
|
return (this.time - other.time).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package it.danieleverducci.lunatracker.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.StatisticsActivity
|
||||||
|
import utils.DailySummary
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class DailySummaryFragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_daily_summary, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
updateUI(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(view: View) {
|
||||||
|
val activity = activity as? StatisticsActivity ?: return
|
||||||
|
val calculator = activity.getCalculator()
|
||||||
|
val period = activity.getSelectedPeriod()
|
||||||
|
|
||||||
|
// Get today's summary and average for comparison
|
||||||
|
val todaySummary = calculator.getTodaySummary()
|
||||||
|
val feedingStats = calculator.getFeedingStats(period)
|
||||||
|
val sleepStats = calculator.getSleepStats(period)
|
||||||
|
val diaperStats = calculator.getDiaperStats(period)
|
||||||
|
|
||||||
|
// Date header
|
||||||
|
val dateFormat = SimpleDateFormat("EEEE, d MMMM", Locale.getDefault())
|
||||||
|
view.findViewById<TextView>(R.id.date_header).text = dateFormat.format(Date())
|
||||||
|
|
||||||
|
// Bottle summary
|
||||||
|
val bottleSummary = view.findViewById<TextView>(R.id.bottle_summary)
|
||||||
|
val bottleProgress = view.findViewById<ProgressBar>(R.id.bottle_progress)
|
||||||
|
val avgBottle = feedingStats.avgBottleMlPerDay.toInt()
|
||||||
|
bottleSummary.text = "${todaySummary.totalBottleMl} ml (${todaySummary.bottleCount}×) | Ø $avgBottle ml"
|
||||||
|
if (avgBottle > 0) {
|
||||||
|
bottleProgress.max = (avgBottle * 1.5).toInt()
|
||||||
|
bottleProgress.progress = todaySummary.totalBottleMl
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breastfeeding summary
|
||||||
|
val breastfeedingContainer = view.findViewById<LinearLayout>(R.id.breastfeeding_container)
|
||||||
|
val breastfeedingSummary = view.findViewById<TextView>(R.id.breastfeeding_summary)
|
||||||
|
val breastfeedingProgress = view.findViewById<ProgressBar>(R.id.breastfeeding_progress)
|
||||||
|
|
||||||
|
if (todaySummary.breastfeedingCount > 0 || feedingStats.avgBreastfeedingMinPerDay > 0) {
|
||||||
|
breastfeedingContainer.visibility = View.VISIBLE
|
||||||
|
val avgBf = feedingStats.avgBreastfeedingMinPerDay.toInt()
|
||||||
|
breastfeedingSummary.text = "${todaySummary.totalBreastfeedingMin} min (${todaySummary.breastfeedingCount}×) | Ø $avgBf min"
|
||||||
|
if (avgBf > 0) {
|
||||||
|
breastfeedingProgress.max = (avgBf * 1.5).toInt()
|
||||||
|
breastfeedingProgress.progress = todaySummary.totalBreastfeedingMin
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breastfeedingContainer.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sleep summary
|
||||||
|
val sleepSummary = view.findViewById<TextView>(R.id.sleep_summary)
|
||||||
|
val sleepProgress = view.findViewById<ProgressBar>(R.id.sleep_progress)
|
||||||
|
val avgSleepMin = sleepStats.avgSleepMinPerDay.toInt()
|
||||||
|
val todaySleepHours = todaySummary.totalSleepMin / 60f
|
||||||
|
val avgSleepHours = avgSleepMin / 60f
|
||||||
|
sleepSummary.text = String.format(Locale.getDefault(), "%.1f h (%d×) | Ø %.1f h",
|
||||||
|
todaySleepHours, todaySummary.sleepCount, avgSleepHours)
|
||||||
|
if (avgSleepMin > 0) {
|
||||||
|
sleepProgress.max = (avgSleepMin * 1.5).toInt()
|
||||||
|
sleepProgress.progress = todaySummary.totalSleepMin
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diaper summaries
|
||||||
|
val pooSummary = view.findViewById<TextView>(R.id.poo_summary)
|
||||||
|
val peeSummary = view.findViewById<TextView>(R.id.pee_summary)
|
||||||
|
pooSummary.text = String.format(Locale.getDefault(), "%d× | Ø %.1f",
|
||||||
|
todaySummary.diaperPooCount, diaperStats.avgPooPerDay)
|
||||||
|
peeSummary.text = String.format(Locale.getDefault(), "%d× | Ø %.1f",
|
||||||
|
todaySummary.diaperPeeCount, diaperStats.avgPeePerDay)
|
||||||
|
|
||||||
|
// Health card (weight/temperature)
|
||||||
|
val healthCard = view.findViewById<LinearLayout>(R.id.health_card)
|
||||||
|
val weightSummary = view.findViewById<TextView>(R.id.weight_summary)
|
||||||
|
val tempSummary = view.findViewById<TextView>(R.id.temperature_summary)
|
||||||
|
|
||||||
|
if (todaySummary.latestWeight != null || todaySummary.latestTemperature != null) {
|
||||||
|
healthCard.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
if (todaySummary.latestWeight != null) {
|
||||||
|
val weightKg = todaySummary.latestWeight / 1000f
|
||||||
|
weightSummary.text = "⚖️ ${String.format(Locale.getDefault(), "%.2f kg", weightKg)}"
|
||||||
|
weightSummary.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
weightSummary.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todaySummary.latestTemperature != null) {
|
||||||
|
val tempC = todaySummary.latestTemperature / 10f
|
||||||
|
tempSummary.text = "🌡️ ${String.format(Locale.getDefault(), "%.1f °C", tempC)}"
|
||||||
|
tempSummary.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
tempSummary.visibility = View.GONE
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
healthCard.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
package it.danieleverducci.lunatracker.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.StatisticsActivity
|
||||||
|
import utils.DateUtils
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class DiaperStatsFragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_diaper_stats, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
updateUI(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(view: View) {
|
||||||
|
val activity = activity as? StatisticsActivity ?: return
|
||||||
|
val calculator = activity.getCalculator()
|
||||||
|
val period = activity.getSelectedPeriod()
|
||||||
|
|
||||||
|
val stats = calculator.getDiaperStats(period)
|
||||||
|
|
||||||
|
// Draw stacked bar chart
|
||||||
|
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
|
||||||
|
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
|
||||||
|
chartContainer.removeAllViews()
|
||||||
|
chartLabels.removeAllViews()
|
||||||
|
|
||||||
|
val sortedDays = stats.dailyPooCount.keys.sorted().takeLast(period)
|
||||||
|
var maxValue = 1
|
||||||
|
for (day in sortedDays) {
|
||||||
|
val total = (stats.dailyPooCount[day] ?: 0) + (stats.dailyPeeCount[day] ?: 0)
|
||||||
|
if (total > maxValue) maxValue = total
|
||||||
|
}
|
||||||
|
|
||||||
|
val dateFormat = SimpleDateFormat("E", Locale.getDefault())
|
||||||
|
|
||||||
|
for (day in sortedDays) {
|
||||||
|
val pooCount = stats.dailyPooCount[day] ?: 0
|
||||||
|
val peeCount = stats.dailyPeeCount[day] ?: 0
|
||||||
|
val total = pooCount + peeCount
|
||||||
|
|
||||||
|
// Bar container
|
||||||
|
val barContainer = LinearLayout(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
||||||
|
setPadding(4, 0, 4, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pee bar (lighter, on bottom)
|
||||||
|
if (peeCount > 0) {
|
||||||
|
val peeHeight = (peeCount.toFloat() / maxValue * 100).toInt()
|
||||||
|
val peeBar = View(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(peeHeight * resources.displayMetrics.density).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundColor(0x66FFE68F.toInt()) // Semi-transparent accent
|
||||||
|
}
|
||||||
|
barContainer.addView(peeBar, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Poo bar (solid, on top)
|
||||||
|
if (pooCount > 0) {
|
||||||
|
val pooHeight = (pooCount.toFloat() / maxValue * 100).toInt()
|
||||||
|
val pooBar = View(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(pooHeight * resources.displayMetrics.density).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundColor(resources.getColor(R.color.accent, null))
|
||||||
|
}
|
||||||
|
barContainer.addView(pooBar, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
chartContainer.addView(barContainer)
|
||||||
|
|
||||||
|
// Label
|
||||||
|
val label = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
|
text = dateFormat.format(Date(day * 1000))
|
||||||
|
textSize = 10f
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
chartLabels.addView(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary stats
|
||||||
|
view.findViewById<TextView>(R.id.avg_diapers).text =
|
||||||
|
getString(R.string.stats_avg_diapers, stats.avgDiapersPerDay)
|
||||||
|
view.findViewById<TextView>(R.id.avg_poo).text =
|
||||||
|
getString(R.string.stats_avg_poo, stats.avgPooPerDay)
|
||||||
|
view.findViewById<TextView>(R.id.avg_pee).text =
|
||||||
|
getString(R.string.stats_avg_pee, stats.avgPeePerDay)
|
||||||
|
|
||||||
|
// Last poo
|
||||||
|
val lastPoo = view.findViewById<TextView>(R.id.last_poo)
|
||||||
|
if (stats.lastPooTime != null) {
|
||||||
|
val timeAgo = DateUtils.formatTimeAgo(requireContext(), stats.lastPooTime)
|
||||||
|
lastPoo.text = getString(R.string.stats_last_poo, timeAgo)
|
||||||
|
lastPoo.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
lastPoo.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
package it.danieleverducci.lunatracker.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.ProgressBar
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.StatisticsActivity
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class FeedingStatsFragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_feeding_stats, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
updateUI(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(view: View) {
|
||||||
|
val activity = activity as? StatisticsActivity ?: return
|
||||||
|
val calculator = activity.getCalculator()
|
||||||
|
val period = activity.getSelectedPeriod()
|
||||||
|
|
||||||
|
val stats = calculator.getFeedingStats(period)
|
||||||
|
|
||||||
|
// Draw bar chart
|
||||||
|
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
|
||||||
|
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
|
||||||
|
chartContainer.removeAllViews()
|
||||||
|
chartLabels.removeAllViews()
|
||||||
|
|
||||||
|
val sortedDays = stats.dailyBottleTotals.keys.sorted().takeLast(period)
|
||||||
|
val maxValue = (stats.dailyBottleTotals.values.maxOrNull() ?: 1).coerceAtLeast(1)
|
||||||
|
val dateFormat = SimpleDateFormat("E", Locale.getDefault())
|
||||||
|
|
||||||
|
for (day in sortedDays) {
|
||||||
|
val value = stats.dailyBottleTotals[day] ?: 0
|
||||||
|
|
||||||
|
// Bar
|
||||||
|
val barContainer = LinearLayout(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
||||||
|
setPadding(4, 0, 4, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val barHeight = if (maxValue > 0) (value.toFloat() / maxValue * 100).toInt() else 0
|
||||||
|
val bar = View(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
0
|
||||||
|
).apply {
|
||||||
|
height = (barHeight * resources.displayMetrics.density).toInt()
|
||||||
|
}
|
||||||
|
setBackgroundColor(resources.getColor(R.color.accent, null))
|
||||||
|
}
|
||||||
|
barContainer.addView(bar)
|
||||||
|
chartContainer.addView(barContainer)
|
||||||
|
|
||||||
|
// Label
|
||||||
|
val label = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
|
text = dateFormat.format(Date(day * 1000))
|
||||||
|
textSize = 10f
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
chartLabels.addView(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bottle stats
|
||||||
|
val bottleAvg = view.findViewById<TextView>(R.id.bottle_avg_daily)
|
||||||
|
bottleAvg.text = getString(R.string.stats_avg_ml_per_day, stats.avgBottleMlPerDay)
|
||||||
|
|
||||||
|
val feedingInterval = view.findViewById<TextView>(R.id.feeding_interval)
|
||||||
|
feedingInterval.text = getString(R.string.stats_feeding_interval, stats.avgFeedingIntervalMinutes.toInt())
|
||||||
|
|
||||||
|
// Breastfeeding stats
|
||||||
|
val breastfeedingCard = view.findViewById<LinearLayout>(R.id.breastfeeding_card)
|
||||||
|
val totalBreastfeeding = stats.leftBreastCount + stats.rightBreastCount + stats.bothBreastCount
|
||||||
|
|
||||||
|
if (totalBreastfeeding > 0) {
|
||||||
|
breastfeedingCard.visibility = View.VISIBLE
|
||||||
|
|
||||||
|
val avgDuration = view.findViewById<TextView>(R.id.breastfeeding_avg_duration)
|
||||||
|
avgDuration.text = getString(R.string.stats_avg_duration, stats.avgBreastfeedingDuration)
|
||||||
|
|
||||||
|
// Side distribution (excluding "both")
|
||||||
|
val sideTotal = stats.leftBreastCount + stats.rightBreastCount
|
||||||
|
if (sideTotal > 0) {
|
||||||
|
val leftPercent = (stats.leftBreastCount.toFloat() / sideTotal * 100).toInt()
|
||||||
|
val rightPercent = (stats.rightBreastCount.toFloat() / sideTotal * 100).toInt()
|
||||||
|
|
||||||
|
val leftProgress = view.findViewById<ProgressBar>(R.id.left_progress)
|
||||||
|
val rightProgress = view.findViewById<ProgressBar>(R.id.right_progress)
|
||||||
|
val leftPercentText = view.findViewById<TextView>(R.id.left_percent)
|
||||||
|
val rightPercentText = view.findViewById<TextView>(R.id.right_percent)
|
||||||
|
|
||||||
|
leftProgress.max = 100
|
||||||
|
leftProgress.progress = leftPercent
|
||||||
|
rightProgress.max = 100
|
||||||
|
rightProgress.progress = rightPercent
|
||||||
|
leftPercentText.text = "$leftPercent%"
|
||||||
|
rightPercentText.text = "$rightPercent%"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
breastfeedingCard.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
package it.danieleverducci.lunatracker.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.StatisticsActivity
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class GrowthStatsFragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_growth_stats, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
updateUI(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(view: View) {
|
||||||
|
val activity = activity as? StatisticsActivity ?: return
|
||||||
|
val calculator = activity.getCalculator()
|
||||||
|
|
||||||
|
val weightHistory = calculator.getWeightHistory()
|
||||||
|
val noDataMessage = view.findViewById<TextView>(R.id.no_data_message)
|
||||||
|
|
||||||
|
if (weightHistory.isEmpty()) {
|
||||||
|
noDataMessage.visibility = View.VISIBLE
|
||||||
|
view.findViewById<View>(R.id.chart_container).visibility = View.GONE
|
||||||
|
view.findViewById<View>(R.id.chart_labels).visibility = View.GONE
|
||||||
|
view.findViewById<TextView>(R.id.current_weight).visibility = View.GONE
|
||||||
|
view.findViewById<TextView>(R.id.weight_gain_week).visibility = View.GONE
|
||||||
|
view.findViewById<TextView>(R.id.weight_gain_month).visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
noDataMessage.visibility = View.GONE
|
||||||
|
|
||||||
|
// Draw weight chart (line chart approximated with bars)
|
||||||
|
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
|
||||||
|
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
|
||||||
|
chartContainer.removeAllViews()
|
||||||
|
chartLabels.removeAllViews()
|
||||||
|
|
||||||
|
val recentWeights = weightHistory.takeLast(10) // Show last 10 measurements
|
||||||
|
val minWeight = recentWeights.minOfOrNull { it.weightGrams } ?: 0
|
||||||
|
val maxWeight = recentWeights.maxOfOrNull { it.weightGrams } ?: 1
|
||||||
|
val weightRange = (maxWeight - minWeight).coerceAtLeast(100) // At least 100g range
|
||||||
|
val dateFormat = SimpleDateFormat("d/M", Locale.getDefault())
|
||||||
|
|
||||||
|
for (point in recentWeights) {
|
||||||
|
// Bar container
|
||||||
|
val barContainer = LinearLayout(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
||||||
|
setPadding(4, 0, 4, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate relative height (showing weight above minimum)
|
||||||
|
val relativeWeight = point.weightGrams - minWeight + (weightRange * 0.1).toInt()
|
||||||
|
val barHeight = (relativeWeight.toFloat() / (weightRange * 1.2) * 100).toInt()
|
||||||
|
|
||||||
|
val bar = View(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(barHeight * resources.displayMetrics.density).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundColor(resources.getColor(R.color.accent, null))
|
||||||
|
}
|
||||||
|
barContainer.addView(bar)
|
||||||
|
|
||||||
|
// Weight value on top
|
||||||
|
val weightLabel = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT,
|
||||||
|
LinearLayout.LayoutParams.WRAP_CONTENT
|
||||||
|
)
|
||||||
|
val kg = point.weightGrams / 1000f
|
||||||
|
text = String.format(Locale.getDefault(), "%.1f", kg)
|
||||||
|
textSize = 8f
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
barContainer.addView(weightLabel, 0)
|
||||||
|
|
||||||
|
chartContainer.addView(barContainer)
|
||||||
|
|
||||||
|
// Date label
|
||||||
|
val label = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
|
text = dateFormat.format(Date(point.time * 1000))
|
||||||
|
textSize = 10f
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
chartLabels.addView(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Current weight
|
||||||
|
val currentWeight = weightHistory.lastOrNull()
|
||||||
|
if (currentWeight != null) {
|
||||||
|
val kg = currentWeight.weightGrams / 1000f
|
||||||
|
view.findViewById<TextView>(R.id.current_weight).text =
|
||||||
|
getString(R.string.stats_current_weight, String.format(Locale.getDefault(), "%.2f kg", kg))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weight gain calculations
|
||||||
|
val gainWeek = calculator.getWeightGainForDays(7)
|
||||||
|
val gainMonth = calculator.getWeightGainForDays(30)
|
||||||
|
|
||||||
|
val weekView = view.findViewById<TextView>(R.id.weight_gain_week)
|
||||||
|
val monthView = view.findViewById<TextView>(R.id.weight_gain_month)
|
||||||
|
|
||||||
|
if (gainWeek != null) {
|
||||||
|
weekView.text = getString(R.string.stats_weight_gain_week, gainWeek)
|
||||||
|
weekView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
weekView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gainMonth != null) {
|
||||||
|
monthView.text = getString(R.string.stats_weight_gain_month, gainMonth)
|
||||||
|
monthView.visibility = View.VISIBLE
|
||||||
|
} else {
|
||||||
|
monthView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
package it.danieleverducci.lunatracker.fragments
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.Gravity
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import it.danieleverducci.lunatracker.R
|
||||||
|
import it.danieleverducci.lunatracker.StatisticsActivity
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class SleepStatsFragment : Fragment() {
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
return inflater.inflate(R.layout.fragment_sleep_stats, container, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
updateUI(view)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateUI(view: View) {
|
||||||
|
val activity = activity as? StatisticsActivity ?: return
|
||||||
|
val calculator = activity.getCalculator()
|
||||||
|
val period = activity.getSelectedPeriod()
|
||||||
|
|
||||||
|
val stats = calculator.getSleepStats(period)
|
||||||
|
|
||||||
|
// Check if we have any sleep data
|
||||||
|
val hasSleepData = stats.dailyTotals.values.any { it > 0 }
|
||||||
|
val noDataMessage = view.findViewById<TextView>(R.id.no_data_message)
|
||||||
|
|
||||||
|
if (!hasSleepData) {
|
||||||
|
noDataMessage.visibility = View.VISIBLE
|
||||||
|
view.findViewById<View>(R.id.chart_container).visibility = View.GONE
|
||||||
|
view.findViewById<View>(R.id.chart_labels).visibility = View.GONE
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
noDataMessage.visibility = View.GONE
|
||||||
|
|
||||||
|
// Draw bar chart (showing hours per day)
|
||||||
|
val chartContainer = view.findViewById<LinearLayout>(R.id.chart_container)
|
||||||
|
val chartLabels = view.findViewById<LinearLayout>(R.id.chart_labels)
|
||||||
|
chartContainer.removeAllViews()
|
||||||
|
chartLabels.removeAllViews()
|
||||||
|
|
||||||
|
val sortedDays = stats.dailyTotals.keys.sorted().takeLast(period)
|
||||||
|
val maxValue = (stats.dailyTotals.values.maxOrNull() ?: 1).coerceAtLeast(1)
|
||||||
|
val dateFormat = SimpleDateFormat("E", Locale.getDefault())
|
||||||
|
|
||||||
|
for (day in sortedDays) {
|
||||||
|
val minutes = stats.dailyTotals[day] ?: 0
|
||||||
|
|
||||||
|
// Bar container
|
||||||
|
val barContainer = LinearLayout(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f)
|
||||||
|
orientation = LinearLayout.VERTICAL
|
||||||
|
gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
|
||||||
|
setPadding(4, 0, 4, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
val barHeight = if (maxValue > 0) (minutes.toFloat() / maxValue * 100).toInt() else 0
|
||||||
|
val bar = View(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(
|
||||||
|
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||||
|
(barHeight * resources.displayMetrics.density).toInt()
|
||||||
|
)
|
||||||
|
setBackgroundColor(resources.getColor(R.color.accent, null))
|
||||||
|
}
|
||||||
|
barContainer.addView(bar)
|
||||||
|
chartContainer.addView(barContainer)
|
||||||
|
|
||||||
|
// Label
|
||||||
|
val label = TextView(requireContext()).apply {
|
||||||
|
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f)
|
||||||
|
text = dateFormat.format(Date(day * 1000))
|
||||||
|
textSize = 10f
|
||||||
|
gravity = Gravity.CENTER
|
||||||
|
}
|
||||||
|
chartLabels.addView(label)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Summary stats
|
||||||
|
val avgSleepHours = stats.avgSleepMinPerDay / 60f
|
||||||
|
view.findViewById<TextView>(R.id.avg_sleep_per_day).text =
|
||||||
|
getString(R.string.stats_avg_sleep, avgSleepHours)
|
||||||
|
view.findViewById<TextView>(R.id.avg_naps_per_day).text =
|
||||||
|
getString(R.string.stats_avg_naps, stats.avgNapsPerDay)
|
||||||
|
view.findViewById<TextView>(R.id.avg_nap_duration).text =
|
||||||
|
getString(R.string.stats_avg_nap_duration, stats.avgNapDurationMin)
|
||||||
|
view.findViewById<TextView>(R.id.longest_sleep).text =
|
||||||
|
getString(R.string.stats_longest_sleep, stats.longestSleepMin)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,9 +13,9 @@ import java.io.FilenameFilter
|
|||||||
|
|
||||||
class FileLogbookRepository: LogbookRepository {
|
class FileLogbookRepository: LogbookRepository {
|
||||||
companion object {
|
companion object {
|
||||||
val TAG = "FileLogbookRepository"
|
const val TAG = "FileLogbookRepository"
|
||||||
val FILE_NAME_START = "data"
|
const val FILE_NAME_START = "data"
|
||||||
val FILE_NAME_END = ".json"
|
const val FILE_NAME_END = ".json"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
|
override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
|
||||||
@@ -32,7 +32,7 @@ class FileLogbookRepository: LogbookRepository {
|
|||||||
fun loadLogbook(context: Context, name: String): Logbook {
|
fun loadLogbook(context: Context, name: String): Logbook {
|
||||||
val logbook = Logbook(name)
|
val logbook = Logbook(name)
|
||||||
val fileName = getFileName(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 json = FileInputStream(file).bufferedReader().use { it.readText() }
|
||||||
val ja = JSONArray(json)
|
val ja = JSONArray(json)
|
||||||
for (i in 0 until ja.length()) {
|
for (i in 0 until ja.length()) {
|
||||||
@@ -58,7 +58,7 @@ class FileLogbookRepository: LogbookRepository {
|
|||||||
|
|
||||||
fun saveLogbook(context: Context, logbook: Logbook) {
|
fun saveLogbook(context: Context, logbook: Logbook) {
|
||||||
val fileName = getFileName(logbook.name)
|
val fileName = getFileName(logbook.name)
|
||||||
val file = File(context.getFilesDir(), fileName)
|
val file = File(context.filesDir, fileName)
|
||||||
val ja = JSONArray()
|
val ja = JSONArray()
|
||||||
for (l in logbook.logs) {
|
for (l in logbook.logs) {
|
||||||
ja.put(l.toJson())
|
ja.put(l.toJson())
|
||||||
@@ -82,7 +82,7 @@ class FileLogbookRepository: LogbookRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun listLogbooks(context: Context): ArrayList<String> {
|
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 {
|
override fun accept(dir: File?, name: String?): Boolean {
|
||||||
if (name == null)
|
if (name == null)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -3,15 +3,21 @@ package it.danieleverducci.lunatracker.repository
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Context.MODE_PRIVATE
|
import android.content.Context.MODE_PRIVATE
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
import androidx.core.content.edit
|
||||||
|
|
||||||
class LocalSettingsRepository(val context: Context) {
|
class LocalSettingsRepository(val context: Context) {
|
||||||
companion object {
|
companion object {
|
||||||
val SHARED_PREFS_FILE_NAME = "lunasettings"
|
const val SHARED_PREFS_FILE_NAME = "lunasettings"
|
||||||
val SHARED_PREFS_BB_CONTENT = "bbcontent"
|
const val SHARED_PREFS_BB_CONTENT = "bbcontent"
|
||||||
val SHARED_PREFS_DATA_REPO = "data_repo"
|
const val SHARED_PREFS_DATA_REPO = "data_repo"
|
||||||
val SHARED_PREFS_DAV_URL = "webdav_url"
|
const val SHARED_PREFS_DAV_URL = "webdav_url"
|
||||||
val SHARED_PREFS_DAV_USER = "webdav_user"
|
const val SHARED_PREFS_DAV_USER = "webdav_user"
|
||||||
val SHARED_PREFS_DAV_PASS = "webdav_password"
|
const val SHARED_PREFS_DAV_PASS = "webdav_password"
|
||||||
|
const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
|
||||||
|
const val SHARED_PREFS_SIGNATURE = "signature"
|
||||||
|
const val SHARED_PREFS_BF_TIMER_START = "bf_timer_start"
|
||||||
|
const val SHARED_PREFS_BF_TIMER_TYPE = "bf_timer_type"
|
||||||
|
const val SHARED_PREFS_SLEEP_TIMER_START = "sleep_timer_start"
|
||||||
}
|
}
|
||||||
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
|
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
|
||||||
val sharedPreferences: SharedPreferences
|
val sharedPreferences: SharedPreferences
|
||||||
@@ -21,23 +27,39 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveBabyBottleContent(content: Int) {
|
fun saveBabyBottleContent(content: Int) {
|
||||||
sharedPreferences.edit().putInt(SHARED_PREFS_BB_CONTENT, content).apply()
|
sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadBabyBottleContent(): Int {
|
fun loadBabyBottleContent(): Int {
|
||||||
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
|
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) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadNoBreastfeeding(): Boolean {
|
||||||
|
return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false)
|
||||||
|
}
|
||||||
|
|
||||||
fun saveDataRepository(repo: DATA_REPO) {
|
fun saveDataRepository(repo: DATA_REPO) {
|
||||||
val spe = sharedPreferences.edit()
|
sharedPreferences.edit(commit = true) {
|
||||||
spe.putString(
|
putString(
|
||||||
SHARED_PREFS_DATA_REPO,
|
SHARED_PREFS_DATA_REPO,
|
||||||
when (repo) {
|
when (repo) {
|
||||||
DATA_REPO.WEBDAV -> "webdav"
|
DATA_REPO.WEBDAV -> "webdav"
|
||||||
DATA_REPO.LOCAL_FILE -> "localfile"
|
DATA_REPO.LOCAL_FILE -> "localfile"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
spe.commit()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadDataRepository(): DATA_REPO {
|
fun loadDataRepository(): DATA_REPO {
|
||||||
@@ -50,11 +72,11 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveWebdavCredentials(url: String, username: String, password: String) {
|
fun saveWebdavCredentials(url: String, username: String, password: String) {
|
||||||
val spe = sharedPreferences.edit()
|
sharedPreferences.edit(commit = true) {
|
||||||
spe.putString(SHARED_PREFS_DAV_URL, url)
|
putString(SHARED_PREFS_DAV_URL, url)
|
||||||
spe.putString(SHARED_PREFS_DAV_USER, username)
|
putString(SHARED_PREFS_DAV_USER, username)
|
||||||
spe.putString(SHARED_PREFS_DAV_PASS, password)
|
putString(SHARED_PREFS_DAV_PASS, password)
|
||||||
spe.commit()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadWebdavCredentials(): Array<String>? {
|
fun loadWebdavCredentials(): Array<String>? {
|
||||||
@@ -65,4 +87,41 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
return null
|
return null
|
||||||
return arrayOf(url, user, pass)
|
return arrayOf(url, user, pass)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveBreastfeedingTimer(startTime: Long, eventType: String) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putLong(SHARED_PREFS_BF_TIMER_START, startTime)
|
||||||
|
putString(SHARED_PREFS_BF_TIMER_TYPE, eventType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadBreastfeedingTimer(): Pair<Long, String>? {
|
||||||
|
val startTime = sharedPreferences.getLong(SHARED_PREFS_BF_TIMER_START, 0)
|
||||||
|
val eventType = sharedPreferences.getString(SHARED_PREFS_BF_TIMER_TYPE, null)
|
||||||
|
if (startTime == 0L || eventType == null) return null
|
||||||
|
return Pair(startTime, eventType)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearBreastfeedingTimer() {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
remove(SHARED_PREFS_BF_TIMER_START)
|
||||||
|
remove(SHARED_PREFS_BF_TIMER_TYPE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveSleepTimer(startTime: Long) {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
putLong(SHARED_PREFS_SLEEP_TIMER_START, startTime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loadSleepTimer(): Long {
|
||||||
|
return sharedPreferences.getLong(SHARED_PREFS_SLEEP_TIMER_START, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSleepTimer() {
|
||||||
|
sharedPreferences.edit {
|
||||||
|
remove(SHARED_PREFS_SLEEP_TIMER_START)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -103,6 +103,7 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
|||||||
listener: LogbookListObtainedListener
|
listener: LogbookListObtainedListener
|
||||||
) {
|
) {
|
||||||
Thread(Runnable {
|
Thread(Runnable {
|
||||||
|
try {
|
||||||
val logbooksNames = arrayListOf<String>()
|
val logbooksNames = arrayListOf<String>()
|
||||||
for (dr: DavResource in sardine.list(webDavURL)){
|
for (dr: DavResource in sardine.list(webDavURL)){
|
||||||
if(!dr.name.startsWith(FILE_NAME_START))
|
if(!dr.name.startsWith(FILE_NAME_START))
|
||||||
@@ -116,6 +117,18 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
listener.onLogbookListObtained(logbooksNames)
|
listener.onLogbookListObtained(logbooksNames)
|
||||||
|
} catch (e: SardineException) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
listener.onWebDAVError(e)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
listener.onIOError(e)
|
||||||
|
} catch (e: SocketTimeoutException) {
|
||||||
|
Log.e(TAG, e.toString())
|
||||||
|
listener.onIOError(e)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
listener.onError(e)
|
||||||
|
}
|
||||||
}).start()
|
}).start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,77 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
import android.text.format.DateFormat
|
import android.text.format.DateFormat
|
||||||
import it.danieleverducci.lunatracker.R
|
import it.danieleverducci.lunatracker.R
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class DateUtils {
|
class DateUtils {
|
||||||
companion object {
|
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
|
||||||
|
|
||||||
|
val years = (seconds / (365 * 24 * 60 * 60F)).toLong()
|
||||||
|
seconds -= years * (365 * 24 * 60 * 60)
|
||||||
|
val days = (seconds / (24 * 60 * 60F)).toLong()
|
||||||
|
seconds -= days * (24 * 60 * 60)
|
||||||
|
val hours = (seconds / (60 * 60F)).toLong()
|
||||||
|
seconds -= hours * (60 * 60)
|
||||||
|
val minutes = (seconds / 60F).toLong()
|
||||||
|
seconds -= minutes * 60
|
||||||
|
|
||||||
|
fun format(value1: Long, value2: Long, resIdSingular1: Int, resIdPlural1: Int, resIdSingular2: Int, resIdPlural2: Int): String {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
if (value1 == 0L) {
|
||||||
|
// omit
|
||||||
|
} else if (value1 == 1L) {
|
||||||
|
builder.append(value1)
|
||||||
|
builder.append(" ")
|
||||||
|
builder.append(context.getString(resIdSingular1))
|
||||||
|
} else {
|
||||||
|
builder.append(value1)
|
||||||
|
builder.append(" ")
|
||||||
|
builder.append(context.getString(resIdPlural1))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value1 > 0L && value2 > 0L) {
|
||||||
|
builder.append(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value2 == 0L) {
|
||||||
|
// omit
|
||||||
|
} else if (value2 == 1L) {
|
||||||
|
builder.append(value2)
|
||||||
|
builder.append(" ")
|
||||||
|
builder.append(context.getString(resIdSingular2))
|
||||||
|
} else {
|
||||||
|
builder.append(value2)
|
||||||
|
builder.append(" ")
|
||||||
|
builder.append(context.getString(resIdPlural2))
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (years > 0) {
|
||||||
|
return format(years, days, R.string.year_ago, R.string.years_ago, R.string.day_ago, R.string.days_ago)
|
||||||
|
} else if (days > 0) {
|
||||||
|
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
|
||||||
|
} else if (hours > 0) {
|
||||||
|
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
|
||||||
|
} else if (minutes > 0) {
|
||||||
|
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)
|
||||||
|
} else {
|
||||||
|
return context.getString(R.string.now)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
fun formatTimeAgo(context: Context, unixTime: Long): String {
|
||||||
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
|
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
|
||||||
@@ -25,10 +87,10 @@ class DateUtils {
|
|||||||
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
|
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
|
||||||
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
|
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
|
||||||
|
|
||||||
var formattedTime = StringBuilder()
|
val formattedTime = StringBuilder()
|
||||||
if (hoursAgo > 0) {
|
if (hoursAgo > 0) {
|
||||||
formattedTime.append(hoursAgo).append(" ")
|
formattedTime.append(hoursAgo).append(" ")
|
||||||
if (hoursAgo.toInt() == 1)
|
if (hoursAgo == 1)
|
||||||
formattedTime.append(context.getString(R.string.hour_ago))
|
formattedTime.append(context.getString(R.string.hour_ago))
|
||||||
else
|
else
|
||||||
formattedTime.append(context.getString(R.string.hours_ago))
|
formattedTime.append(context.getString(R.string.hours_ago))
|
||||||
@@ -37,12 +99,28 @@ class DateUtils {
|
|||||||
if (formattedTime.isNotEmpty())
|
if (formattedTime.isNotEmpty())
|
||||||
formattedTime.append(", ")
|
formattedTime.append(", ")
|
||||||
formattedTime.append(minutesAgo).append(" ")
|
formattedTime.append(minutesAgo).append(" ")
|
||||||
if (minutesAgo.toInt() == 1)
|
if (minutesAgo == 1)
|
||||||
formattedTime.append(context.getString(R.string.minute_ago))
|
formattedTime.append(context.getString(R.string.minute_ago))
|
||||||
else
|
else
|
||||||
formattedTime.append(context.getString(R.string.minutes_ago))
|
formattedTime.append(context.getString(R.string.minutes_ago))
|
||||||
}
|
}
|
||||||
return formattedTime.toString()
|
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.DEFAULT, 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.content.Context
|
||||||
import android.icu.util.LocaleData
|
import android.icu.util.LocaleData
|
||||||
import android.icu.util.ULocale
|
import android.icu.util.ULocale
|
||||||
|
import android.os.Build
|
||||||
import it.danieleverducci.lunatracker.R
|
import it.danieleverducci.lunatracker.R
|
||||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
@@ -14,29 +15,45 @@ class NumericUtils (val context: Context) {
|
|||||||
val measurement_unit_weight_tiny: String
|
val measurement_unit_weight_tiny: String
|
||||||
val measurement_unit_temperature_base: 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 {
|
init {
|
||||||
this.numberFormat = NumberFormat.getInstance()
|
this.numberFormat = NumberFormat.getInstance()
|
||||||
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
|
|
||||||
this.measurement_unit_liquid_base = context.getString(
|
this.measurement_unit_liquid_base = context.getString(
|
||||||
if (measurementSystem == LocaleData. MeasurementSystem.SI)
|
if (isMetricSystem())
|
||||||
R.string.measurement_unit_liquid_base_metric
|
R.string.measurement_unit_liquid_base_metric
|
||||||
else
|
else
|
||||||
R.string.measurement_unit_liquid_base_imperial
|
R.string.measurement_unit_liquid_base_imperial
|
||||||
)
|
)
|
||||||
this.measurement_unit_weight_base = context.getString(
|
this.measurement_unit_weight_base = context.getString(
|
||||||
if (measurementSystem == LocaleData. MeasurementSystem.SI)
|
if (isMetricSystem())
|
||||||
R.string.measurement_unit_weight_base_metric
|
R.string.measurement_unit_weight_base_metric
|
||||||
else
|
else
|
||||||
R.string.measurement_unit_weight_base_imperial
|
R.string.measurement_unit_weight_base_imperial
|
||||||
)
|
)
|
||||||
this.measurement_unit_weight_tiny = context.getString(
|
this.measurement_unit_weight_tiny = context.getString(
|
||||||
if (measurementSystem == LocaleData. MeasurementSystem.SI)
|
if (isMetricSystem())
|
||||||
R.string.measurement_unit_weight_tiny_metric
|
R.string.measurement_unit_weight_tiny_metric
|
||||||
else
|
else
|
||||||
R.string.measurement_unit_weight_tiny_imperial
|
R.string.measurement_unit_weight_tiny_imperial
|
||||||
)
|
)
|
||||||
this.measurement_unit_temperature_base = context.getString(
|
this.measurement_unit_temperature_base = context.getString(
|
||||||
if (measurementSystem == LocaleData. MeasurementSystem.SI)
|
if (isMetricSystem())
|
||||||
R.string.measurement_unit_temperature_base_metric
|
R.string.measurement_unit_temperature_base_metric
|
||||||
else
|
else
|
||||||
R.string.measurement_unit_temperature_base_imperial
|
R.string.measurement_unit_temperature_base_imperial
|
||||||
@@ -45,11 +62,15 @@ class NumericUtils (val context: Context) {
|
|||||||
|
|
||||||
fun formatEventQuantity(item: LunaEvent): String {
|
fun formatEventQuantity(item: LunaEvent): String {
|
||||||
val formatted = StringBuilder()
|
val formatted = StringBuilder()
|
||||||
if ((item.quantity ?: 0) > 0) {
|
if (item.quantity > 0) {
|
||||||
if (item.type == LunaEvent.TYPE_TEMPERATURE)
|
formatted.append(when (item.type) {
|
||||||
formatted.append((item.quantity / 10.0f).toString())
|
LunaEvent.TYPE_TEMPERATURE ->
|
||||||
else
|
(item.quantity / 10.0f).toString()
|
||||||
formatted.append(item.quantity)
|
LunaEvent.TYPE_PUKE ->
|
||||||
|
context.resources.getStringArray(R.array.AmountLabels)[item.quantity]
|
||||||
|
else ->
|
||||||
|
item.quantity
|
||||||
|
})
|
||||||
|
|
||||||
formatted.append(" ")
|
formatted.append(" ")
|
||||||
formatted.append(
|
formatted.append(
|
||||||
@@ -58,6 +79,11 @@ class NumericUtils (val context: Context) {
|
|||||||
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
|
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
|
||||||
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
|
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
|
||||||
LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
|
LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE,
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE,
|
||||||
|
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE,
|
||||||
|
LunaEvent.TYPE_SLEEP ->
|
||||||
|
context.getString(R.string.measurement_unit_time_minutes)
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -70,10 +96,9 @@ class NumericUtils (val context: Context) {
|
|||||||
* @return min, max, normal
|
* @return min, max, normal
|
||||||
*/
|
*/
|
||||||
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
|
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
|
||||||
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
|
|
||||||
return when (lunaEventType) {
|
return when (lunaEventType) {
|
||||||
LunaEvent.TYPE_TEMPERATURE -> {
|
LunaEvent.TYPE_TEMPERATURE -> {
|
||||||
if (measurementSystem == LocaleData. MeasurementSystem.SI)
|
if (isMetricSystem())
|
||||||
Triple(
|
Triple(
|
||||||
context.resources.getInteger(R.integer.human_body_temp_min_metric),
|
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_max_metric),
|
||||||
|
|||||||
343
app/src/main/java/utils/StatisticsCalculator.kt
Normal file
343
app/src/main/java/utils/StatisticsCalculator.kt
Normal file
@@ -0,0 +1,343 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
|
import java.util.Calendar
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data classes for statistics results
|
||||||
|
*/
|
||||||
|
data class DailySummary(
|
||||||
|
val date: Long,
|
||||||
|
val totalBottleMl: Int,
|
||||||
|
val bottleCount: Int,
|
||||||
|
val totalBreastfeedingMin: Int,
|
||||||
|
val breastfeedingCount: Int,
|
||||||
|
val breastfeedingLeftCount: Int,
|
||||||
|
val breastfeedingRightCount: Int,
|
||||||
|
val totalSleepMin: Int,
|
||||||
|
val sleepCount: Int,
|
||||||
|
val diaperPooCount: Int,
|
||||||
|
val diaperPeeCount: Int,
|
||||||
|
val totalFoodCount: Int,
|
||||||
|
val latestWeight: Int?,
|
||||||
|
val latestTemperature: Int?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class FeedingStats(
|
||||||
|
val dailyBottleTotals: Map<Long, Int>,
|
||||||
|
val dailyBreastfeedingTotals: Map<Long, Int>,
|
||||||
|
val avgBottleMlPerDay: Float,
|
||||||
|
val avgBreastfeedingMinPerDay: Float,
|
||||||
|
val leftBreastCount: Int,
|
||||||
|
val rightBreastCount: Int,
|
||||||
|
val bothBreastCount: Int,
|
||||||
|
val avgBreastfeedingDuration: Float,
|
||||||
|
val avgFeedingIntervalMinutes: Long
|
||||||
|
)
|
||||||
|
|
||||||
|
data class DiaperStats(
|
||||||
|
val dailyPooCount: Map<Long, Int>,
|
||||||
|
val dailyPeeCount: Map<Long, Int>,
|
||||||
|
val avgDiapersPerDay: Float,
|
||||||
|
val avgPooPerDay: Float,
|
||||||
|
val avgPeePerDay: Float,
|
||||||
|
val lastPooTime: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class SleepStats(
|
||||||
|
val dailyTotals: Map<Long, Int>,
|
||||||
|
val avgSleepMinPerDay: Float,
|
||||||
|
val avgNapsPerDay: Float,
|
||||||
|
val avgNapDurationMin: Float,
|
||||||
|
val longestSleepMin: Int,
|
||||||
|
val lastSleepTime: Long?
|
||||||
|
)
|
||||||
|
|
||||||
|
data class WeightPoint(
|
||||||
|
val time: Long,
|
||||||
|
val weightGrams: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
data class TemperaturePoint(
|
||||||
|
val time: Long,
|
||||||
|
val temperatureDeciCelsius: Int
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculator for statistics based on LunaEvent data
|
||||||
|
*/
|
||||||
|
class StatisticsCalculator(private val events: List<LunaEvent>) {
|
||||||
|
|
||||||
|
private fun getStartOfDay(unixTimeSeconds: Long): Long {
|
||||||
|
val cal = Calendar.getInstance()
|
||||||
|
cal.timeInMillis = unixTimeSeconds * 1000
|
||||||
|
cal.set(Calendar.HOUR_OF_DAY, 0)
|
||||||
|
cal.set(Calendar.MINUTE, 0)
|
||||||
|
cal.set(Calendar.SECOND, 0)
|
||||||
|
cal.set(Calendar.MILLISECOND, 0)
|
||||||
|
return cal.timeInMillis / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getEventsInRange(startUnix: Long, endUnix: Long): List<LunaEvent> {
|
||||||
|
return events.filter { it.time >= startUnix && it.time < endUnix }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getEventsForDays(days: Int): List<LunaEvent> {
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val startOfToday = getStartOfDay(now)
|
||||||
|
val startTime = startOfToday - (days - 1) * 24 * 60 * 60
|
||||||
|
return events.filter { it.time >= startTime }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get summary for a specific day (unix timestamp in seconds)
|
||||||
|
*/
|
||||||
|
fun getDailySummary(dayUnix: Long): DailySummary {
|
||||||
|
val startOfDay = getStartOfDay(dayUnix)
|
||||||
|
val endOfDay = startOfDay + 24 * 60 * 60
|
||||||
|
val dayEvents = getEventsInRange(startOfDay, endOfDay)
|
||||||
|
|
||||||
|
val bottleEvents = dayEvents.filter { it.type == LunaEvent.TYPE_BABY_BOTTLE }
|
||||||
|
val breastfeedingEvents = dayEvents.filter {
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE ||
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE ||
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE
|
||||||
|
}
|
||||||
|
val sleepEvents = dayEvents.filter { it.type == LunaEvent.TYPE_SLEEP }
|
||||||
|
val pooEvents = dayEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_POO }
|
||||||
|
val peeEvents = dayEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_PEE }
|
||||||
|
val foodEvents = dayEvents.filter { it.type == LunaEvent.TYPE_FOOD }
|
||||||
|
val weightEvents = dayEvents.filter { it.type == LunaEvent.TYPE_WEIGHT }
|
||||||
|
val tempEvents = dayEvents.filter { it.type == LunaEvent.TYPE_TEMPERATURE }
|
||||||
|
|
||||||
|
return DailySummary(
|
||||||
|
date = startOfDay,
|
||||||
|
totalBottleMl = bottleEvents.sumOf { it.quantity },
|
||||||
|
bottleCount = bottleEvents.size,
|
||||||
|
totalBreastfeedingMin = breastfeedingEvents.sumOf { it.quantity },
|
||||||
|
breastfeedingCount = breastfeedingEvents.size,
|
||||||
|
breastfeedingLeftCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE },
|
||||||
|
breastfeedingRightCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE },
|
||||||
|
totalSleepMin = sleepEvents.sumOf { it.quantity },
|
||||||
|
sleepCount = sleepEvents.size,
|
||||||
|
diaperPooCount = pooEvents.size,
|
||||||
|
diaperPeeCount = peeEvents.size,
|
||||||
|
totalFoodCount = foodEvents.size,
|
||||||
|
latestWeight = weightEvents.maxByOrNull { it.time }?.quantity,
|
||||||
|
latestTemperature = tempEvents.maxByOrNull { it.time }?.quantity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get today's summary
|
||||||
|
*/
|
||||||
|
fun getTodaySummary(): DailySummary {
|
||||||
|
return getDailySummary(System.currentTimeMillis() / 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get feeding statistics for the last N days
|
||||||
|
*/
|
||||||
|
fun getFeedingStats(days: Int): FeedingStats {
|
||||||
|
val relevantEvents = getEventsForDays(days)
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val startOfToday = getStartOfDay(now)
|
||||||
|
|
||||||
|
// Daily totals
|
||||||
|
val dailyBottleTotals = mutableMapOf<Long, Int>()
|
||||||
|
val dailyBreastfeedingTotals = mutableMapOf<Long, Int>()
|
||||||
|
|
||||||
|
for (i in 0 until days) {
|
||||||
|
val dayStart = startOfToday - i * 24 * 60 * 60
|
||||||
|
dailyBottleTotals[dayStart] = 0
|
||||||
|
dailyBreastfeedingTotals[dayStart] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val bottleEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_BABY_BOTTLE }
|
||||||
|
val breastfeedingEvents = relevantEvents.filter {
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE ||
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE ||
|
||||||
|
it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE
|
||||||
|
}
|
||||||
|
|
||||||
|
bottleEvents.forEach { event ->
|
||||||
|
val dayStart = getStartOfDay(event.time)
|
||||||
|
dailyBottleTotals[dayStart] = (dailyBottleTotals[dayStart] ?: 0) + event.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
breastfeedingEvents.forEach { event ->
|
||||||
|
val dayStart = getStartOfDay(event.time)
|
||||||
|
dailyBreastfeedingTotals[dayStart] = (dailyBreastfeedingTotals[dayStart] ?: 0) + event.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
// Breastfeeding side distribution
|
||||||
|
val leftCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE }
|
||||||
|
val rightCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE }
|
||||||
|
val bothCount = breastfeedingEvents.count { it.type == LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE }
|
||||||
|
|
||||||
|
// Average breastfeeding duration
|
||||||
|
val avgBreastfeedingDuration = if (breastfeedingEvents.isNotEmpty()) {
|
||||||
|
breastfeedingEvents.sumOf { it.quantity }.toFloat() / breastfeedingEvents.size
|
||||||
|
} else 0f
|
||||||
|
|
||||||
|
// Average feeding interval (all feeding events sorted by time)
|
||||||
|
val allFeedingEvents = (bottleEvents + breastfeedingEvents).sortedBy { it.time }
|
||||||
|
val avgFeedingIntervalMinutes = if (allFeedingEvents.size > 1) {
|
||||||
|
var totalInterval = 0L
|
||||||
|
for (i in 1 until allFeedingEvents.size) {
|
||||||
|
totalInterval += allFeedingEvents[i].time - allFeedingEvents[i-1].time
|
||||||
|
}
|
||||||
|
(totalInterval / (allFeedingEvents.size - 1)) / 60
|
||||||
|
} else 0L
|
||||||
|
|
||||||
|
return FeedingStats(
|
||||||
|
dailyBottleTotals = dailyBottleTotals,
|
||||||
|
dailyBreastfeedingTotals = dailyBreastfeedingTotals,
|
||||||
|
avgBottleMlPerDay = if (days > 0) dailyBottleTotals.values.sum().toFloat() / days else 0f,
|
||||||
|
avgBreastfeedingMinPerDay = if (days > 0) dailyBreastfeedingTotals.values.sum().toFloat() / days else 0f,
|
||||||
|
leftBreastCount = leftCount,
|
||||||
|
rightBreastCount = rightCount,
|
||||||
|
bothBreastCount = bothCount,
|
||||||
|
avgBreastfeedingDuration = avgBreastfeedingDuration,
|
||||||
|
avgFeedingIntervalMinutes = avgFeedingIntervalMinutes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get diaper statistics for the last N days
|
||||||
|
*/
|
||||||
|
fun getDiaperStats(days: Int): DiaperStats {
|
||||||
|
val relevantEvents = getEventsForDays(days)
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val startOfToday = getStartOfDay(now)
|
||||||
|
|
||||||
|
val dailyPooCount = mutableMapOf<Long, Int>()
|
||||||
|
val dailyPeeCount = mutableMapOf<Long, Int>()
|
||||||
|
|
||||||
|
for (i in 0 until days) {
|
||||||
|
val dayStart = startOfToday - i * 24 * 60 * 60
|
||||||
|
dailyPooCount[dayStart] = 0
|
||||||
|
dailyPeeCount[dayStart] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val pooEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_POO }
|
||||||
|
val peeEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_DIAPERCHANGE_PEE }
|
||||||
|
|
||||||
|
pooEvents.forEach { event ->
|
||||||
|
val dayStart = getStartOfDay(event.time)
|
||||||
|
dailyPooCount[dayStart] = (dailyPooCount[dayStart] ?: 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
peeEvents.forEach { event ->
|
||||||
|
val dayStart = getStartOfDay(event.time)
|
||||||
|
dailyPeeCount[dayStart] = (dailyPeeCount[dayStart] ?: 0) + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalDiapers = pooEvents.size + peeEvents.size
|
||||||
|
|
||||||
|
return DiaperStats(
|
||||||
|
dailyPooCount = dailyPooCount,
|
||||||
|
dailyPeeCount = dailyPeeCount,
|
||||||
|
avgDiapersPerDay = if (days > 0) totalDiapers.toFloat() / days else 0f,
|
||||||
|
avgPooPerDay = if (days > 0) pooEvents.size.toFloat() / days else 0f,
|
||||||
|
avgPeePerDay = if (days > 0) peeEvents.size.toFloat() / days else 0f,
|
||||||
|
lastPooTime = pooEvents.maxByOrNull { it.time }?.time
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get sleep statistics for the last N days
|
||||||
|
*/
|
||||||
|
fun getSleepStats(days: Int): SleepStats {
|
||||||
|
val relevantEvents = getEventsForDays(days)
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val startOfToday = getStartOfDay(now)
|
||||||
|
|
||||||
|
val dailyTotals = mutableMapOf<Long, Int>()
|
||||||
|
|
||||||
|
for (i in 0 until days) {
|
||||||
|
val dayStart = startOfToday - i * 24 * 60 * 60
|
||||||
|
dailyTotals[dayStart] = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val sleepEvents = relevantEvents.filter { it.type == LunaEvent.TYPE_SLEEP }
|
||||||
|
|
||||||
|
sleepEvents.forEach { event ->
|
||||||
|
val dayStart = getStartOfDay(event.time)
|
||||||
|
dailyTotals[dayStart] = (dailyTotals[dayStart] ?: 0) + event.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
val totalSleepMin = sleepEvents.sumOf { it.quantity }
|
||||||
|
val avgNapDuration = if (sleepEvents.isNotEmpty()) {
|
||||||
|
totalSleepMin.toFloat() / sleepEvents.size
|
||||||
|
} else 0f
|
||||||
|
|
||||||
|
val longestSleep = sleepEvents.maxOfOrNull { it.quantity } ?: 0
|
||||||
|
|
||||||
|
return SleepStats(
|
||||||
|
dailyTotals = dailyTotals,
|
||||||
|
avgSleepMinPerDay = if (days > 0) totalSleepMin.toFloat() / days else 0f,
|
||||||
|
avgNapsPerDay = if (days > 0) sleepEvents.size.toFloat() / days else 0f,
|
||||||
|
avgNapDurationMin = avgNapDuration,
|
||||||
|
longestSleepMin = longestSleep,
|
||||||
|
lastSleepTime = sleepEvents.maxByOrNull { it.time }?.time
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get weight history (all weight measurements)
|
||||||
|
*/
|
||||||
|
fun getWeightHistory(): List<WeightPoint> {
|
||||||
|
return events
|
||||||
|
.filter { it.type == LunaEvent.TYPE_WEIGHT && it.quantity > 0 }
|
||||||
|
.sortedBy { it.time }
|
||||||
|
.map { WeightPoint(it.time, it.quantity) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get temperature history
|
||||||
|
*/
|
||||||
|
fun getTemperatureHistory(): List<TemperaturePoint> {
|
||||||
|
return events
|
||||||
|
.filter { it.type == LunaEvent.TYPE_TEMPERATURE && it.quantity > 0 }
|
||||||
|
.sortedBy { it.time }
|
||||||
|
.map { TemperaturePoint(it.time, it.quantity) }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate weight gain over the last N days
|
||||||
|
*/
|
||||||
|
fun getWeightGainForDays(days: Int): Int? {
|
||||||
|
val weights = getWeightHistory()
|
||||||
|
if (weights.size < 2) return null
|
||||||
|
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
val startTime = now - days * 24 * 60 * 60
|
||||||
|
|
||||||
|
val recentWeight = weights.lastOrNull() ?: return null
|
||||||
|
val olderWeight = weights.filter { it.time <= startTime }.lastOrNull()
|
||||||
|
?: weights.firstOrNull()
|
||||||
|
?: return null
|
||||||
|
|
||||||
|
if (recentWeight.time == olderWeight.time) return null
|
||||||
|
|
||||||
|
return recentWeight.weightGrams - olderWeight.weightGrams
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get average daily values for a type of event over N days
|
||||||
|
*/
|
||||||
|
fun getAverageDailyCount(type: String, days: Int): Float {
|
||||||
|
val relevantEvents = getEventsForDays(days).filter { it.type == type }
|
||||||
|
return if (days > 0) relevantEvents.size.toFloat() / days else 0f
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get average daily quantity sum for a type of event over N days
|
||||||
|
*/
|
||||||
|
fun getAverageDailyQuantity(type: String, days: Int): Float {
|
||||||
|
val relevantEvents = getEventsForDays(days).filter { it.type == type }
|
||||||
|
val total = relevantEvents.sumOf { it.quantity }
|
||||||
|
return if (days > 0) total.toFloat() / days else 0f
|
||||||
|
}
|
||||||
|
}
|
||||||
12
app/src/main/res/drawable/ic_back.xml
Normal file
12
app/src/main/res/drawable/ic_back.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:tint="#000000">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_edit.xml
Normal file
5
app/src/main/res/drawable/ic_edit.xml
Normal 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="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
12
app/src/main/res/drawable/ic_sleep.xml
Normal file
12
app/src/main/res/drawable/ic_sleep.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:tint="#000000">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M12,3c-4.97,0 -9,4.03 -9,9s4.03,9 9,9s9,-4.03 9,-9c0,-0.46 -0.04,-0.92 -0.1,-1.36c-0.98,1.37 -2.58,2.26 -4.4,2.26c-2.98,0 -5.4,-2.42 -5.4,-5.4c0,-1.81 0.89,-3.42 2.26,-4.4C12.92,3.04 12.46,3 12,3L12,3z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
16
app/src/main/res/drawable/ic_statistics.xml
Normal file
16
app/src/main/res/drawable/ic_statistics.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:tint="#000000">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M16,6l2.29,2.29 -4.88,4.88 -4,-4L2,16.59 3.41,18l6,-6 4,4 6.3,-6.29L22,12V6z"/>
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:fillColor="@android:color/white"
|
||||||
|
android:pathData="M4,20h16v2H4z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
17
app/src/main/res/drawable/progress_bar_accent.xml
Normal file
17
app/src/main/res/drawable/progress_bar_accent.xml
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:id="@android:id/background">
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
<solid android:color="#33FFFFFF"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:id="@android:id/progress">
|
||||||
|
<clip>
|
||||||
|
<shape>
|
||||||
|
<corners android:radius="4dp"/>
|
||||||
|
<solid android:color="@color/accent"/>
|
||||||
|
</shape>
|
||||||
|
</clip>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
@@ -16,6 +16,15 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/button_statistics"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:src="@drawable/ic_statistics"
|
||||||
|
app:tint="@color/grey"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
@@ -30,7 +39,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:text="@string/title"
|
android:text="@string/title"
|
||||||
android:textSize="26dp"
|
android:textSize="26sp"
|
||||||
android:gravity="center"/>
|
android:gravity="center"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
@@ -47,7 +56,7 @@
|
|||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="38dp"
|
android:layout_height="38dp"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:orientation="horizontal"
|
android:orientation="horizontal"
|
||||||
android:gravity="center_vertical">
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
@@ -68,23 +77,17 @@
|
|||||||
android:id="@+id/logbooks_add_button"
|
android:id="@+id/logbooks_add_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:paddingLeft="16dp"
|
android:paddingLeft="16dp"
|
||||||
android:paddingRight="16dp"
|
android:paddingRight="16dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:textSize="20dp"
|
android:textSize="20sp"
|
||||||
android:text="+"
|
android:text="+"
|
||||||
android:background="@drawable/button_background"/>
|
android:background="@drawable/button_background"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="@string/log_an_event"
|
|
||||||
android:gravity="center_horizontal"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/buttons_container"
|
android:id="@+id/buttons_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -101,60 +104,61 @@
|
|||||||
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:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="50dp"
|
android:textSize="50sp"
|
||||||
android:text="@string/event_bottle_type"/>
|
android:text="@string/event_bottle_type"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_scale"
|
android:id="@+id/button_food"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="50dp"
|
android:textSize="50sp"
|
||||||
android:text="@string/event_scale_type"/>
|
android:text="@string/event_food_type"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content"
|
||||||
|
android:id="@+id/layout_nipples">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_nipple_left"
|
android:id="@+id/button_nipple_left"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:text="🤱⬅️"/>
|
android:text="🤱⬅️"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_nipple_both"
|
android:id="@+id/button_nipple_both"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:text="🤱↔️"/>
|
android:text="🤱↔️"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_nipple_right"
|
android:id="@+id/button_nipple_right"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:text="🤱➡️️"/>
|
android:text="🤱➡️️"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -167,29 +171,29 @@
|
|||||||
android:id="@+id/button_change_poo"
|
android:id="@+id/button_change_poo"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="2"
|
android:layout_weight="2"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:text="🚼 💩"/>
|
android:text="🚼 💩"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_change_pee"
|
android:id="@+id/button_change_pee"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="2"
|
android:layout_weight="2"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:text="🚼 💧"/>
|
android:text="🚼 💧"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/button_more"
|
android:id="@+id/button_more"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="10dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
@@ -200,22 +204,32 @@
|
|||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
<LinearLayout
|
||||||
android:id="@+id/progress_indicator"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="10dp"
|
android:layout_marginTop="5dp"
|
||||||
android:indeterminate="true"
|
android:orientation="horizontal">
|
||||||
app:indicatorColor="@color/accent"
|
|
||||||
android:visibility="invisible"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="@string/logbook"
|
android:text="@string/logbook"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:textStyle="bold"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/progress_indicator"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:indicatorColor="@color/accent"
|
||||||
|
android:visibility="invisible"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
android:id="@+id/list_events"
|
android:id="@+id/list_events"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -247,7 +261,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30dp"
|
android:textSize="30sp"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:text="@string/no_connection"/>
|
android:text="@string/no_connection"/>
|
||||||
|
|
||||||
@@ -264,7 +278,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:drawableLeft="@drawable/ic_sync"
|
android:drawableStart="@drawable/ic_sync"
|
||||||
android:drawableTint="@color/white"
|
android:drawableTint="@color/white"
|
||||||
android:drawablePadding="10dp"
|
android:drawablePadding="10dp"
|
||||||
android:text="@string/no_connection_retry"
|
android:text="@string/no_connection_retry"
|
||||||
@@ -275,7 +289,7 @@
|
|||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:drawableLeft="@drawable/ic_settings"
|
android:drawableStart="@drawable/ic_settings"
|
||||||
android:drawableTint="@color/white"
|
android:drawableTint="@color/white"
|
||||||
android:drawablePadding="10dp"
|
android:drawablePadding="10dp"
|
||||||
android:text="@string/no_connection_go_to_settings"
|
android:text="@string/no_connection_go_to_settings"
|
||||||
|
|||||||
@@ -39,26 +39,26 @@
|
|||||||
<TextView
|
<TextView
|
||||||
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_marginStart="30dp"
|
||||||
android:text="@string/settings_storage_local_desc"/>
|
android:text="@string/settings_storage_local_desc"/>
|
||||||
|
|
||||||
<RadioButton android:id="@+id/settings_data_webdav"
|
<RadioButton android:id="@+id/settings_data_webdav"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="30dp"
|
android:layout_marginTop="20dp"
|
||||||
android:text="@string/settings_storage_dav"/>
|
android:text="@string/settings_storage_dav"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
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_marginStart="30dp"
|
||||||
android:text="@string/settings_storage_dav_desc"/>
|
android:text="@string/settings_storage_dav_desc"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginLeft="30dp"
|
android:layout_marginStart="30dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="@string/settings_storage_dav_url"/>
|
android:text="@string/settings_storage_dav_url"/>
|
||||||
|
|
||||||
@@ -66,7 +66,7 @@
|
|||||||
android:id="@+id/settings_data_webdav_url"
|
android:id="@+id/settings_data_webdav_url"
|
||||||
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_marginStart="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"/>
|
android:background="@drawable/textview_background"/>
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginLeft="30dp"
|
android:layout_marginStart="30dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="@string/settings_storage_dav_user"/>
|
android:text="@string/settings_storage_dav_user"/>
|
||||||
|
|
||||||
@@ -83,7 +83,7 @@
|
|||||||
android:id="@+id/settings_data_webdav_user"
|
android:id="@+id/settings_data_webdav_user"
|
||||||
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_marginStart="30dp"
|
||||||
android:inputType="textEmailAddress"
|
android:inputType="textEmailAddress"
|
||||||
android:background="@drawable/textview_background"/>
|
android:background="@drawable/textview_background"/>
|
||||||
|
|
||||||
@@ -91,17 +91,23 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:layout_marginLeft="30dp"
|
android:layout_marginStart="30dp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="@string/settings_storage_dav_pass"/>
|
android:text="@string/settings_storage_dav_pass"/>
|
||||||
|
|
||||||
<EditText
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
app:passwordToggleEnabled="true">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/settings_data_webdav_pass"
|
android:id="@+id/settings_data_webdav_pass"
|
||||||
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:background="@drawable/textview_background"
|
||||||
android:inputType="textPassword"
|
android:inputType="textPassword" />
|
||||||
android:background="@drawable/textview_background"/>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
android:id="@+id/progress_indicator"
|
android:id="@+id/progress_indicator"
|
||||||
@@ -109,12 +115,103 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:indeterminate="true"
|
android:indeterminate="true"
|
||||||
app:indicatorColor="@color/accent"
|
app:indicatorColor="@color/accent"
|
||||||
android:layout_margin="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:visibility="invisible"/>
|
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
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:text="@string/settings_no_breastfeeding" />
|
||||||
|
|
||||||
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
|
android:id="@+id/switch_no_breastfeeding"
|
||||||
|
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"/>
|
||||||
|
|
||||||
|
<!-- Data Backup Section -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="30dp"
|
||||||
|
android:text="@string/settings_backup_title"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/settings_export"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:textColor="@color/accent"
|
||||||
|
android:text="@string/settings_export"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:text="@string/settings_export_desc"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/settings_import"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:textColor="@color/accent"
|
||||||
|
android:text="@string/settings_import"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="30dp"
|
||||||
|
android:text="@string/settings_import_desc"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
@@ -122,7 +219,7 @@
|
|||||||
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:layout_marginEnd="20dp"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:text="@android:string/cancel"/>
|
android:text="@android:string/cancel"/>
|
||||||
@@ -137,7 +234,6 @@
|
|||||||
android:text="@android:string/ok"/>
|
android:text="@android:string/ok"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
</RadioGroup>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
57
app/src/main/res/layout/activity_statistics.xml
Normal file
57
app/src/main/res/layout/activity_statistics.xml
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingHorizontal="15dp">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/button_back"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:src="@drawable/ic_back"
|
||||||
|
app:tint="@color/grey"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/statistics_title"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/statistics_title"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/period_spinner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minWidth="80dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.tabs.TabLayout
|
||||||
|
android:id="@+id/tab_layout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
app:tabMode="scrollable"
|
||||||
|
app:tabGravity="start"
|
||||||
|
app:tabTextColor="@color/grey"
|
||||||
|
app:tabSelectedTextColor="@color/accent"
|
||||||
|
app:tabIndicatorColor="@color/accent"/>
|
||||||
|
|
||||||
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
|
android:id="@+id/view_pager"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
20
app/src/main/res/layout/breastfeeding_duration_dialog.xml
Normal file
20
app/src/main/res/layout/breastfeeding_duration_dialog.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/breastfeeding_duration_picker"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="@string/measurement_unit_time_minutes"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
35
app/src/main/res/layout/breastfeeding_timer_dialog.xml
Normal file
35
app/src/main/res/layout/breastfeeding_timer_dialog.xml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?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"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/breastfeeding_side_emoji"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="60sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/breastfeeding_timer_display"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:textColor="@color/accent"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:text="00:00"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/grey"
|
||||||
|
android:text="@string/breastfeeding_timer_hint"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,43 +1,52 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="20dp">
|
android:paddingTop="20dp"
|
||||||
|
android:paddingBottom="10dp"
|
||||||
|
android:paddingHorizontal="20dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dialog_event_detail_type_emoji"
|
android:id="@+id/dialog_event_detail_type_emoji"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="60dp"
|
android:textSize="60sp"
|
||||||
android:text="@string/event_diaperchange_pee_type" />
|
android:text="@string/event_diaperchange_pee_type" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dialog_event_detail_type_description"
|
android:id="@+id/dialog_event_detail_type_description"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="12dp"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:textSize="24dp"
|
android:textSize="32sp"
|
||||||
android:text="@string/event_diaperchange_pee_desc" />
|
android:text="@string/event_diaperchange_pee_desc" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dialog_event_detail_type_date"
|
android:id="@+id/dialog_event_detail_type_date"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="40dp"
|
||||||
android:layout_marginTop="20dp"
|
android:layout_marginTop="20dp"
|
||||||
android:gravity="center_horizontal"
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:drawableTint="@color/accent"
|
||||||
|
android:textSize="16sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="2020"/>
|
android:text="@string/dialog_event_detail_datetime_icon"
|
||||||
|
app:drawableEndCompat="@drawable/ic_edit" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dialog_event_detail_type_quantity"
|
android:id="@+id/dialog_event_detail_type_quantity"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:text="2020"/>
|
android:textSize="28sp"
|
||||||
|
android:text="@string/dialog_event_detail_quantity"/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -49,7 +58,45 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textStyle="italic"
|
android:textStyle="italic"
|
||||||
android:text="Lorem ipsum"/>
|
android:textSize="20sp"
|
||||||
|
android:text="@string/dialog_event_detail_notes"/>
|
||||||
|
|
||||||
</ScrollView>
|
</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"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_event_previous"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
<Space
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_event_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="10dp"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:text="" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
274
app/src/main/res/layout/fragment_daily_summary.xml
Normal file
274
app/src/main/res/layout/fragment_daily_summary.xml
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
<?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="15dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/date_header"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textColor="@color/accent"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginBottom="20dp"/>
|
||||||
|
|
||||||
|
<!-- Feeding Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_feeding_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_bottle_type"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottle_summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/bottle_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
|
android:progressDrawable="@drawable/progress_bar_accent"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/breastfeeding_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_breastfeeding_both_type"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/breastfeeding_summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/breastfeeding_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
|
android:progressDrawable="@drawable/progress_bar_accent"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Sleep Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_sleep_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_sleep_type"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:layout_marginStart="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sleep_summary"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/sleep_progress"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="8dp"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
|
android:progressDrawable="@drawable/progress_bar_accent"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Diapers Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_diapers_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_diaperchange_poo_type"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/poo_summary"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_diaperchange_pee_type"
|
||||||
|
android:textSize="24sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/pee_summary"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="center_vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Weight/Temperature Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/health_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_health_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/temperature_summary"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
129
app/src/main/res/layout/fragment_diaper_stats.xml
Normal file
129
app/src/main/res/layout/fragment_diaper_stats.xml
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
<?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="15dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Daily Chart -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_diapers_per_day"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="bottom"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_labels"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:background="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:layout_marginEnd="20dp"
|
||||||
|
android:text="@string/stats_poo"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="16dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:background="#66FFE68F"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="5dp"
|
||||||
|
android:text="@string/stats_pee"
|
||||||
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Summary Stats -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_summary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_diapers"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_poo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_pee"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/last_poo"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
175
app/src/main/res/layout/fragment_feeding_stats.xml
Normal file
175
app/src/main/res/layout/fragment_feeding_stats.xml
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
<?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="15dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Daily Chart -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_daily_intake"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="bottom"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_labels"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Bottle Stats -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_bottle_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/bottle_avg_daily"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/feeding_interval"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Breastfeeding Stats -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/breastfeeding_card"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_breastfeeding_title"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/breastfeeding_avg_duration"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="15dp"
|
||||||
|
android:text="@string/stats_side_distribution"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_left"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/left_progress"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
|
android:progressDrawable="@drawable/progress_bar_accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/left_percent"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="5dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_right"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/right_progress"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="16dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
style="@android:style/Widget.ProgressBar.Horizontal"
|
||||||
|
android:progressDrawable="@drawable/progress_bar_accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/right_percent"
|
||||||
|
android:layout_width="50dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:gravity="end"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
100
app/src/main/res/layout/fragment_growth_stats.xml
Normal file
100
app/src/main/res/layout/fragment_growth_stats.xml
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
<?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="15dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Weight Chart -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_weight_curve"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="150dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="bottom"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_labels"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Weight Summary -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_weight_summary"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/current_weight"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_gain_week"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/weight_gain_month"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- No Data Message -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_data_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="40dp"
|
||||||
|
android:text="@string/stats_no_weight_data"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/grey"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
106
app/src/main/res/layout/fragment_sleep_stats.xml
Normal file
106
app/src/main/res/layout/fragment_sleep_stats.xml
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
<?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="15dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<!-- Daily Chart -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_sleep_per_day"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="bottom"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/chart_labels"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Sleep Summary -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:padding="15dp"
|
||||||
|
android:layout_marginBottom="10dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/stats_sleep_analysis"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_sleep_per_day"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_naps_per_day"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/avg_nap_duration"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/longest_sleep"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- No Data Message -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_data_message"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="40dp"
|
||||||
|
android:text="@string/stats_no_sleep_data"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/grey"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="20dp"
|
android:padding="10dp"
|
||||||
android:background="@color/transparent">
|
android:background="@color/transparent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
@@ -10,31 +10,31 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button_sleep"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
|
style="@style/OverflowMenuText"
|
||||||
|
android:text="@string/overflow_event_sleep"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_medicine"
|
android:id="@+id/button_medicine"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="20dp"
|
android:layout_marginTop="10dp"
|
||||||
|
android:padding="10dp"
|
||||||
android:background="@drawable/dropdown_list_item_background"
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
style="@style/OverflowMenuText"
|
style="@style/OverflowMenuText"
|
||||||
android:text="@string/overflow_event_medicine"/>
|
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
|
<TextView
|
||||||
android:id="@+id/button_note"
|
android:id="@+id/button_note"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:padding="20dp"
|
android:padding="10dp"
|
||||||
android:background="@drawable/dropdown_list_item_background"
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
style="@style/OverflowMenuText"
|
style="@style/OverflowMenuText"
|
||||||
android:text="@string/overflow_event_note"/>
|
android:text="@string/overflow_event_note"/>
|
||||||
@@ -44,21 +44,61 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:padding="20dp"
|
android:padding="10dp"
|
||||||
android:background="@drawable/dropdown_list_item_background"
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
style="@style/OverflowMenuText"
|
style="@style/OverflowMenuText"
|
||||||
android:text="@string/overflow_event_temperature"/>
|
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
|
<TextView
|
||||||
android:id="@+id/button_colic"
|
android:id="@+id/button_colic"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_marginTop="10dp"
|
android:layout_marginTop="10dp"
|
||||||
android:padding="20dp"
|
android:padding="10dp"
|
||||||
android:background="@drawable/dropdown_list_item_background"
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
style="@style/OverflowMenuText"
|
style="@style/OverflowMenuText"
|
||||||
android:text="@string/overflow_event_colic"/>
|
android:text="@string/overflow_event_colic"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button_scale"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
|
style="@style/OverflowMenuText"
|
||||||
|
android:text="@string/overflow_event_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>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
@@ -17,6 +17,6 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:text="g"/>
|
android:text="g"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -14,6 +14,6 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginLeft="10dp"
|
android:layout_marginStart="10dp"
|
||||||
android:text="ml"/>
|
android:text="ml"/>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
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>
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
android:id="@+id/type"
|
android:id="@+id/type"
|
||||||
android:layout_width="90dp"
|
android:layout_width="90dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="10dp"
|
android:paddingStart="10dp"
|
||||||
android:textSize="28sp"
|
android:textSize="28sp"
|
||||||
android:lines="1"
|
android:lines="1"
|
||||||
android:maxLines="1"
|
android:maxLines="1"
|
||||||
@@ -25,7 +25,7 @@
|
|||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:text="Description"/>
|
android:text="@string/row_luna_event_description"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/quantity"
|
android:id="@+id/quantity"
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
android:layout_marginLeft="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:layout_marginRight="5dp"
|
android:layout_marginRight="5dp"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:text="Qty"/>
|
android:text="@string/row_luna_event_quantity"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/time"
|
android:id="@+id/time"
|
||||||
@@ -46,6 +46,6 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:text="Time"/>
|
android:text="@string/row_luna_event_time"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|||||||
@@ -31,7 +31,7 @@
|
|||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textColor="@color/accent"
|
android:textColor="@color/accent"
|
||||||
android:text="Description"/>
|
android:text="@string/row_luna_event_description"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/quantity"
|
android:id="@+id/quantity"
|
||||||
@@ -41,7 +41,7 @@
|
|||||||
android:layout_marginLeft="5dp"
|
android:layout_marginLeft="5dp"
|
||||||
android:layout_marginRight="5dp"
|
android:layout_marginRight="5dp"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:text="Qty"/>
|
android:text="@string/dialog_event_detail_quantity"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/time"
|
android:id="@+id/time"
|
||||||
@@ -52,7 +52,7 @@
|
|||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:ellipsize="end"
|
android:ellipsize="end"
|
||||||
android:maxLines="2"
|
android:maxLines="2"
|
||||||
android:text="Time"/>
|
android:text="@string/row_luna_event_time"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
20
app/src/main/res/layout/sleep_duration_dialog.xml
Normal file
20
app/src/main/res/layout/sleep_duration_dialog.xml
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/sleep_duration_picker"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="@string/measurement_unit_time_minutes"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
36
app/src/main/res/layout/sleep_timer_dialog.xml
Normal file
36
app/src/main/res/layout/sleep_timer_dialog.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<?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"
|
||||||
|
android:padding="20dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sleep_emoji"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/event_sleep_type"
|
||||||
|
android:textSize="60sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/sleep_timer_display"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:textSize="48sp"
|
||||||
|
android:textColor="@color/accent"
|
||||||
|
android:fontFamily="monospace"
|
||||||
|
android:text="00:00"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textColor="@color/grey"
|
||||||
|
android:text="@string/sleep_timer_hint"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
217
app/src/main/res/values-de/strings.xml
Normal file
217
app/src/main/res/values-de/strings.xml
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">LunaTracker</string>
|
||||||
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
|
<string name="logbook">Ereignisprotokoll</string>
|
||||||
|
|
||||||
|
<string name="log_bottle_dialog_title">Fläschchen</string>
|
||||||
|
<string name="log_bottle_dialog_description">Trinkmenge eingeben</string>
|
||||||
|
|
||||||
|
<string name="log_weight_dialog_title">Gewicht</string>
|
||||||
|
<string name="log_weight_dialog_description">Gewicht eingeben</string>
|
||||||
|
|
||||||
|
<string name="log_temperature_dialog_title">Temperatur</string>
|
||||||
|
<string name="log_temperature_dialog_description">Temperatur eingeben</string>
|
||||||
|
|
||||||
|
<string name="event_bottle_desc">Fläschchen</string>
|
||||||
|
<string name="event_food_desc">Essen</string>
|
||||||
|
<string name="event_scale_desc">Gewicht</string>
|
||||||
|
<string name="event_breastfeeding_left_desc">Stillen (links)</string>
|
||||||
|
<string name="event_breastfeeding_both_desc">Stillen</string>
|
||||||
|
<string name="event_breastfeeding_right_desc">Stillen (rechts)</string>
|
||||||
|
<string name="event_diaperchange_poo_desc">Windelwechsel (Stuhl)</string>
|
||||||
|
<string name="event_diaperchange_pee_desc">Windelwechsel (Urin)</string>
|
||||||
|
<string name="event_medicine_desc">Medikament</string>
|
||||||
|
<string name="event_enema_desc">Einlauf</string>
|
||||||
|
<string name="event_note_desc">Notiz</string>
|
||||||
|
<string name="event_temperature_desc">Temperatur</string>
|
||||||
|
<string name="event_colic_desc">Blähungskolik</string>
|
||||||
|
<string name="event_unknown_desc"></string>
|
||||||
|
|
||||||
|
<string name="overflow_event_scale">⚖️ Gewicht</string>
|
||||||
|
<string name="overflow_event_medicine">💊 Medikament</string>
|
||||||
|
<string name="overflow_event_enema">🪠 Einlauf</string>
|
||||||
|
<string name="overflow_event_note">📝 Notiz</string>
|
||||||
|
<string name="overflow_event_temperature">🌡️ Temperatur</string>
|
||||||
|
<string name="overflow_event_colic">💨 Blähungskolik</string>
|
||||||
|
|
||||||
|
<string name="toast_event_added">Ereignis gespeichert</string>
|
||||||
|
<string name="toast_logbook_saved">Logbuch gespeichert</string>
|
||||||
|
<string name="toast_event_add_error">Ereignis konnte nicht protokolliert werden</string>
|
||||||
|
<string name="toast_integer_error">Ungültiger Wert. Bitte eine Ganzzahl eingeben.</string>
|
||||||
|
|
||||||
|
<string name="now">jetzt</string>
|
||||||
|
<string name="hour_ago">Std.</string>
|
||||||
|
<string name="hours_ago">Std.</string>
|
||||||
|
<string name="minute_ago">Min.</string>
|
||||||
|
<string name="minutes_ago">Min.</string>
|
||||||
|
|
||||||
|
<string name="no_connection">Keine Verbindung</string>
|
||||||
|
<string name="no_connection_explain">WebDAV-Dienst nicht erreichbar</string>
|
||||||
|
<string name="no_connection_go_to_settings">Einstellungen</string>
|
||||||
|
<string name="no_connection_retry">Erneut versuchen</string>
|
||||||
|
|
||||||
|
<string name="settings_title">Einstellungen</string>
|
||||||
|
<string name="settings_no_breastfeeding">Kein Stillen</string>
|
||||||
|
<string name="settings_storage">Speicherort für Daten auswählen</string>
|
||||||
|
<string name="settings_storage_local">Auf dem Gerät</string>
|
||||||
|
<string name="settings_storage_local_desc">Datenschutzfreundlichste Lösung: Deine Daten verlassen dein Gerät nicht</string>
|
||||||
|
<string name="settings_storage_dav">Auf einem WebDAV-Server</string>
|
||||||
|
<string name="settings_storage_dav_desc">Du kannst jeden WebDAV-Dienst (z. B. Nextcloud, ownCloud, Dropbox, …) verwenden, um die Daten zu speichern. Auf diese Weise kannst du sie zwischen mehreren Geräten synchronisieren (z. B. dem vom Vater, der Mutter, der Großmutter, …). Dazu benötigst du die WebDAV-URL. Du findest diese in der Dokumentation deines Anbieters (z. B. bei Nextcloud im Bereich Einstellungen der Dateien-App).</string>
|
||||||
|
<string name="settings_storage_dav_url">WebDAV URL</string>
|
||||||
|
<string name="settings_storage_dav_url_hint">https://</string>
|
||||||
|
<string name="settings_storage_dav_user">Benutzername</string>
|
||||||
|
<string name="settings_storage_dav_pass">Passwort</string>
|
||||||
|
<string name="settings_network_error">Server nicht erreichbar: </string>
|
||||||
|
<string name="settings_webdav_error_denied">Falscher WebDAV-Benutzer oder falsches Passwort</string>
|
||||||
|
<string name="settings_webdav_error_server_offline">WebDAV-Server ist derzeit nicht verfügbar</string>
|
||||||
|
<string name="settings_webdav_error_generic">Fehler beim Zugriff auf WebDAV:</string>
|
||||||
|
<string name="settings_webdav_creation_error_generic">Eine Datei konnte auf dem WebDAV-Server nicht gespeichert werden:</string>
|
||||||
|
<string name="settings_webdav_creation_ok">Erfolgreich mit dem WebDAV-Server verbunden</string>
|
||||||
|
<string name="settings_json_error">Es befindet sich eine Speicherdatei auf dem Server, aber sie ist beschädigt oder unlesbar. Bitte lösche die Datei.</string>
|
||||||
|
<string name="settings_generic_error">Fehler: </string>
|
||||||
|
<string name="settings_webdav_upload_error">Fehler beim Hochladen des lokalen Logbuchs %1$s zu WebDAV: %2$s</string>
|
||||||
|
|
||||||
|
<string name="trim_logbook_dialog_title">Dein Logbuch ist ziemlich groß!</string>
|
||||||
|
<string name="trim_logbook_dialog_message_local">Deine Logbuchdatei wird sehr groß. Wir empfehlen, die ältesten Einträge zu entfernen, um Abstürze zu vermeiden.</string>
|
||||||
|
<string name="trim_logbook_dialog_message_dav">Deine Logbuchdatei wird sehr groß. Wir empfehlen, die ältesten Einträge (durch Entfernen) zu bereinigen, um Abstürze zu vermeiden. Wenn du alle Einträge behalten möchtest, sichere bitte die Datei "lunatracker_logbook.json" auf dem WebDAV‑Server oder benenne sie um, um ein neues Logbuch zu beginnen und das alte zu behalten.</string>
|
||||||
|
<string name="trim_logbook_dialog_button_ok">Jetzt bereinigen</string>
|
||||||
|
<string name="trim_logbook_dialog_button_cancel">Später erinnern</string>
|
||||||
|
|
||||||
|
<string name="log_notes_dialog_description">Notizen:</string>
|
||||||
|
<string name="log_medicine_dialog_description">Medikamentenname, Menge, Art, Notizen, …:</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Menge (optional)</string>
|
||||||
|
<string name="log_notes_dialog_note_hint">Notiz eingeben</string>
|
||||||
|
|
||||||
|
<string name="dialog_event_detail_title">Ereignisdetails</string>
|
||||||
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
<string name="dialog_event_detail_delete_button">Löschen</string>
|
||||||
|
|
||||||
|
<string name="dialog_add_logbook_title">Logbuch hinzufügen</string>
|
||||||
|
<string name="dialog_add_logbook_logbookname">👶 Logbuchname</string>
|
||||||
|
<string name="dialog_add_logbook_message">Gib einen Namen ein, um dieses Logbuch zu bezeichnen. Dieser Name erscheint oben auf dem Bildschirm und wird, falls du WebDAV verwendest, auch im Dateinamen der gespeicherten Datei enthalten sein.</string>
|
||||||
|
<string name="dialog_add_logbook_message_intro">Willkommen! Um diese App zu benutzen, musst du mindestens ein Logbuch erstellen. Am besten benennst du es nach dem Namen deines Kindes.</string>
|
||||||
|
|
||||||
|
<string name="default_logbook_name">👶 Mein erstes Logbuch</string>
|
||||||
|
<string name="logbook_created">Neues Logbuch erstellt: </string>
|
||||||
|
|
||||||
|
<string name="breastfeeding_timer_title">Stillen läuft</string>
|
||||||
|
<string name="breastfeeding_timer_stop">Stopp</string>
|
||||||
|
<string name="breastfeeding_timer_hint">Tippe Stopp wenn fertig</string>
|
||||||
|
<string name="breastfeeding_timer_already_running">Es läuft bereits eine Stillsitzung</string>
|
||||||
|
<string name="breastfeeding_duration_title">Stilldauer</string>
|
||||||
|
<string name="breastfeeding_duration_description">Dauer in Minuten eingeben</string>
|
||||||
|
|
||||||
|
<!-- Puke/Bath Events -->
|
||||||
|
<string name="log_puke_dialog_title">Spucken</string>
|
||||||
|
<string name="log_puke_dialog_description">Menge auswählen</string>
|
||||||
|
<string name="event_puke_desc">Spucken</string>
|
||||||
|
<string name="event_bath_desc">Baden</string>
|
||||||
|
<string name="overflow_event_puke">🤮 Spucken</string>
|
||||||
|
<string name="overflow_event_bath">🛁 Baden</string>
|
||||||
|
|
||||||
|
<!-- Zeitangaben -->
|
||||||
|
<string name="second_ago">Sek.</string>
|
||||||
|
<string name="seconds_ago">Sek.</string>
|
||||||
|
<string name="day_ago">Tag</string>
|
||||||
|
<string name="days_ago">Tage</string>
|
||||||
|
<string name="year_ago">Jahr</string>
|
||||||
|
<string name="years_ago">Jahre</string>
|
||||||
|
|
||||||
|
<!-- Mengenangaben -->
|
||||||
|
<string name="amount_little">Wenig</string>
|
||||||
|
<string name="amount_normal">Normal</string>
|
||||||
|
<string name="amount_plenty">Viel</string>
|
||||||
|
|
||||||
|
<!-- Signatur-Einstellungen -->
|
||||||
|
<string name="settings_signature">Signatur</string>
|
||||||
|
<string name="settings_signature_desc">Füge jedem Event eine Signatur hinzu, die andere sehen können. Nützlich wenn mehrere Personen Events hinzufügen.</string>
|
||||||
|
<string name="settings_no_breastfeeding_desc">Verstecke die Stillbuttons wenn sie nicht benötigt werden.</string>
|
||||||
|
|
||||||
|
<!-- Event-Detail-Dialog -->
|
||||||
|
<string name="dialog_event_detail_quantity">Menge</string>
|
||||||
|
<string name="dialog_event_detail_notes">Notizen</string>
|
||||||
|
<string name="dialog_event_detail_signature">von %s</string>
|
||||||
|
|
||||||
|
<!-- Schlaf-Tracking -->
|
||||||
|
<string name="event_sleep_desc">Schlaf</string>
|
||||||
|
<string name="sleep_timer_title">Baby schläft</string>
|
||||||
|
<string name="sleep_timer_stop">Aufgewacht</string>
|
||||||
|
<string name="sleep_timer_hint">Tippen wenn Baby aufwacht</string>
|
||||||
|
<string name="sleep_timer_already_running">Es läuft bereits eine Schlafsitzung</string>
|
||||||
|
<string name="sleep_duration_title">Schlafdauer</string>
|
||||||
|
<string name="sleep_duration_description">Dauer in Minuten eingeben</string>
|
||||||
|
<string name="overflow_event_sleep">🌙 Schlaf</string>
|
||||||
|
|
||||||
|
<!-- Statistik -->
|
||||||
|
<string name="statistics_title">Statistik</string>
|
||||||
|
<string name="stats_tab_today">Heute</string>
|
||||||
|
<string name="stats_tab_feeding">Fütterung</string>
|
||||||
|
<string name="stats_tab_diapers">Windeln</string>
|
||||||
|
<string name="stats_tab_sleep">Schlaf</string>
|
||||||
|
<string name="stats_tab_growth">Wachstum</string>
|
||||||
|
|
||||||
|
<string name="stats_period_7days">7 Tage</string>
|
||||||
|
<string name="stats_period_14days">14 Tage</string>
|
||||||
|
<string name="stats_period_30days">30 Tage</string>
|
||||||
|
|
||||||
|
<string name="stats_feeding_title">Fütterung</string>
|
||||||
|
<string name="stats_sleep_title">Schlaf</string>
|
||||||
|
<string name="stats_diapers_title">Windeln</string>
|
||||||
|
<string name="stats_health_title">Gesundheit</string>
|
||||||
|
|
||||||
|
<string name="stats_today">Heute</string>
|
||||||
|
<string name="stats_times">%d mal</string>
|
||||||
|
<string name="stats_count_format">%d× heute</string>
|
||||||
|
<string name="stats_avg_format">Ø: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_daily_intake">Tägliche Aufnahme</string>
|
||||||
|
<string name="stats_bottle_title">Fläschchen</string>
|
||||||
|
<string name="stats_breastfeeding_title">Stillen</string>
|
||||||
|
<string name="stats_avg_per_day">Ø: %.1f/Tag</string>
|
||||||
|
<string name="stats_avg_ml_per_day">Ø: %.0f ml/Tag</string>
|
||||||
|
<string name="stats_avg_min_per_day">Ø: %.0f min/Tag</string>
|
||||||
|
<string name="stats_feeding_interval">Fütterungsintervall: Ø %d min</string>
|
||||||
|
<string name="stats_avg_duration">Ø Dauer: %.1f min</string>
|
||||||
|
<string name="stats_side_distribution">Seitenverteilung</string>
|
||||||
|
<string name="stats_left">Links</string>
|
||||||
|
<string name="stats_right">Rechts</string>
|
||||||
|
|
||||||
|
<string name="stats_diapers_per_day">Windeln pro Tag</string>
|
||||||
|
<string name="stats_poo">Stuhl</string>
|
||||||
|
<string name="stats_pee">Urin</string>
|
||||||
|
<string name="stats_summary">Zusammenfassung</string>
|
||||||
|
<string name="stats_avg_diapers">Ø: %.1f Windeln/Tag</string>
|
||||||
|
<string name="stats_avg_poo">Stuhl: %.1f/Tag</string>
|
||||||
|
<string name="stats_avg_pee">Urin: %.1f/Tag</string>
|
||||||
|
<string name="stats_last_poo">Letzter Stuhl: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_sleep_per_day">Schlaf pro Tag</string>
|
||||||
|
<string name="stats_sleep_analysis">Schlafanalyse</string>
|
||||||
|
<string name="stats_avg_sleep">Ø: %.1f Stunden/Tag</string>
|
||||||
|
<string name="stats_avg_naps">Ø: %.1f Schläfchen/Tag</string>
|
||||||
|
<string name="stats_avg_nap_duration">Ø Schläfchen: %.0f min</string>
|
||||||
|
<string name="stats_longest_sleep">Längstes: %d min</string>
|
||||||
|
<string name="stats_no_sleep_data">Noch keine Schlafdaten erfasst</string>
|
||||||
|
<string name="stats_hours_format">%.1f Std</string>
|
||||||
|
|
||||||
|
<string name="stats_weight_curve">Gewichtskurve</string>
|
||||||
|
<string name="stats_weight_summary">Gewicht</string>
|
||||||
|
<string name="stats_current_weight">Aktuell: %s</string>
|
||||||
|
<string name="stats_weight_gain_week">Letzte 7 Tage: %+d g</string>
|
||||||
|
<string name="stats_weight_gain_month">Letzte 30 Tage: %+d g</string>
|
||||||
|
<string name="stats_no_weight_data">Noch keine Gewichtsdaten erfasst</string>
|
||||||
|
<string name="stats_weight_format">%.2f kg</string>
|
||||||
|
<string name="stats_temperature_format">%.1f °C</string>
|
||||||
|
|
||||||
|
<!-- Export/Import -->
|
||||||
|
<string name="settings_backup_title">Datensicherung</string>
|
||||||
|
<string name="settings_export">Logbook exportieren</string>
|
||||||
|
<string name="settings_export_desc">Alle Ereignisse als JSON-Datei speichern</string>
|
||||||
|
<string name="settings_import">Logbook importieren</string>
|
||||||
|
<string name="settings_import_desc">Ereignisse aus JSON-Datei laden</string>
|
||||||
|
<string name="export_success">%d Ereignisse exportiert</string>
|
||||||
|
<string name="export_error">Export fehlgeschlagen: </string>
|
||||||
|
<string name="import_success">%d Ereignisse importiert</string>
|
||||||
|
<string name="import_error">Import fehlgeschlagen</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
185
app/src/main/res/values-fr/strings.xml
Normal file
185
app/src/main/res/values-fr/strings.xml
Normal file
@@ -0,0 +1,185 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">LunaTracker</string>
|
||||||
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
|
<string name="logbook">Entrées enregistrées</string>
|
||||||
|
|
||||||
|
<string name="log_bottle_dialog_title">Biberon</string>
|
||||||
|
<string name="log_bottle_dialog_description">Renseignez la quantité contenue dans le biberon</string>
|
||||||
|
|
||||||
|
<string name="log_weight_dialog_title">Poids</string>
|
||||||
|
<string name="log_weight_dialog_description">Renseignez le poids</string>
|
||||||
|
|
||||||
|
<string name="log_temperature_dialog_title">Température</string>
|
||||||
|
<string name="log_temperature_dialog_description">Renseignez la Température</string>
|
||||||
|
|
||||||
|
<string name="event_bottle_desc">Biberon</string>
|
||||||
|
<string name="event_food_desc">Nourriture</string>
|
||||||
|
<string name="event_scale_desc">Poids</string>
|
||||||
|
<string name="event_breastfeeding_left_desc">Allaitement (sein gauche)</string>
|
||||||
|
<string name="event_breastfeeding_both_desc">Allaitement</string>
|
||||||
|
<string name="event_breastfeeding_right_desc">Allaitement (sein droit)</string>
|
||||||
|
<string name="event_diaperchange_poo_desc">Change (selle)</string>
|
||||||
|
<string name="event_diaperchange_pee_desc">Change (urine)</string>
|
||||||
|
<string name="event_medicine_desc">Médicament</string>
|
||||||
|
<string name="event_enema_desc">Lavement</string>
|
||||||
|
<string name="event_note_desc">Note</string>
|
||||||
|
<string name="event_temperature_desc">Température</string>
|
||||||
|
<string name="event_colic_desc">Colique gazeuse</string>
|
||||||
|
<string name="event_unknown_desc"></string>
|
||||||
|
|
||||||
|
<string name="overflow_event_scale">⚖️ Poids</string>
|
||||||
|
<string name="overflow_event_medicine">💊 Médicament</string>
|
||||||
|
<string name="overflow_event_enema">🪠 Lavement</string>
|
||||||
|
<string name="overflow_event_note">📝 Note</string>
|
||||||
|
<string name="overflow_event_temperature">🌡️ Température</string>
|
||||||
|
<string name="overflow_event_colic">💨 Colique gazeuse</string>
|
||||||
|
|
||||||
|
<string name="toast_event_added">Entrée ajoutée</string>
|
||||||
|
<string name="toast_logbook_saved">Journal ajouté</string>
|
||||||
|
<string name="toast_event_add_error">Impossible d\'enregistrer cette entrée</string>
|
||||||
|
<string name="toast_integer_error">Valeur invalide. Renseignez un nombre.</string>
|
||||||
|
|
||||||
|
<string name="now">maintenant </string>
|
||||||
|
<string name="hour_ago">heure</string>
|
||||||
|
<string name="hours_ago">heures</string>
|
||||||
|
<string name="minute_ago">min</string>
|
||||||
|
<string name="minutes_ago">mins</string>
|
||||||
|
|
||||||
|
<string name="no_connection">Pas de connexion internet</string>
|
||||||
|
<string name="no_connection_explain">Impossible de se connecter au service WebDAV</string>
|
||||||
|
<string name="no_connection_go_to_settings">Paramètres</string>
|
||||||
|
<string name="no_connection_retry">Réessayer</string>
|
||||||
|
|
||||||
|
<string name="settings_title">Paramètres</string>
|
||||||
|
<string name="settings_storage">Choisir le lieu de stockage des données</string>
|
||||||
|
<string name="settings_storage_local">Sur l\'appareil\'</string>
|
||||||
|
<string name="settings_storage_local_desc">La solution la plus respectueuse de la vie privée : les données ne quittent pas l\'appareil</string>
|
||||||
|
<string name="settings_storage_dav">Sur un serveur WebDAV</string>
|
||||||
|
<string name="settings_storage_dav_desc">Vous pouvez utiliser n\'importe quel service WebDAV (par exemple Nextcloud, Owncloud, Dropbox, Box...) pour sauvegarder les données. De cette façon, vous pouvez les synchroniser entre plusieurs appareils (Ex : celui de papa, celui de maman, celui de grand-mère...). Vous aurez besoin de l\'adresse WebDAV, que vous trouverez dans la documentation de votre fournisseur (par exemple, pour Nextcloud web, vous la trouverez dans les paramètres de l\'application Fichiers).</string>
|
||||||
|
<string name="settings_storage_dav_url">URL WebDAV</string>
|
||||||
|
<string name="settings_storage_dav_url_hint">https://</string>
|
||||||
|
<string name="settings_storage_dav_user">Nom d\'utilisateur</string>
|
||||||
|
<string name="settings_storage_dav_pass">Mot de passe</string>
|
||||||
|
<string name="settings_network_error">Impossible d\'accéder au serveur: </string>
|
||||||
|
<string name="settings_webdav_error_denied">Nom d\'utilisateur ou mot de passe incorrect pour le service WebDAV</string>
|
||||||
|
<string name="settings_webdav_error_server_offline">Impossible d\'accéder au serveur WebDAV</string>
|
||||||
|
<string name="settings_webdav_error_generic">Une erreur est survenue en essayant d\'accéder au serveur WebDAV:</string>
|
||||||
|
<string name="settings_webdav_creation_error_generic">Impossible de sauvegarder un fichier sur le serveur WebDAV:</string>
|
||||||
|
<string name="settings_webdav_creation_ok">Connexion réussie avec le serveur WebDAV</string>
|
||||||
|
<string name="settings_json_error">Il y a un fichier sur le serveur WebDAV, toutefois il est corronpu ou illisible. Merci de le supprimer et réessayer</string>
|
||||||
|
<string name="settings_generic_error">Erreur: </string>
|
||||||
|
<string name="settings_webdav_upload_error">Une erreur est survenue en téléversant le journal local %1$s sur %2$s</string>
|
||||||
|
|
||||||
|
<string name="trim_logbook_dialog_title">Votre journal grossit !</string>
|
||||||
|
<string name="trim_logbook_dialog_message_local">Le fichier de votre journal a beaucoup grossi. Nous recommandons de supprimer les entrées les plus vieilles pour éviter des crashs de l\'application.</string>
|
||||||
|
<string name="trim_logbook_dialog_message_dav">Le fichier de votre journal a beaucoup grossi. Nous recommandons de supprimer les entrées les plus vieilles pour éviter des crashs de l\'application. Si vous voulez garder un historique, vous pouvez sauvegarder le fichier "lunatracker_logbook.json" ou le renommer pour créer un nouveau journal.</string>
|
||||||
|
<string name="trim_logbook_dialog_button_ok">Supprimer les vieilles entrées maintenant</string>
|
||||||
|
<string name="trim_logbook_dialog_button_cancel">Me rappeller plus tard</string>
|
||||||
|
|
||||||
|
<string name="log_notes_dialog_description">Notes:</string>
|
||||||
|
<string name="log_medicine_dialog_description">nom du médicament, quantité, type, notes …:</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Quantité (ou vide)</string>
|
||||||
|
<string name="log_notes_dialog_note_hint">Notes ...</string>
|
||||||
|
|
||||||
|
<string name="dialog_event_detail_title">Détails de l\'entrée</string>
|
||||||
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
<string name="dialog_event_detail_delete_button">Supprimer</string>
|
||||||
|
|
||||||
|
<string name="dialog_add_logbook_title">Ajouter un journal</string>
|
||||||
|
<string name="dialog_add_logbook_logbookname">👶 Nom du journal</string>
|
||||||
|
|
||||||
|
<string name="dialog_add_logbook_message">Renseignez un nom pour identifier ce journal. Ce nom apparaîtra en haut de l\'écran et, si vous utilisez WebDAV, il figurera également dans le nom du fichier enreigstré.</string>
|
||||||
|
<string name="dialog_add_logbook_message_intro">Bienvenue ! Pour utiliser cette application, vous devez créer au moins un carnet de bord. Vous voudrez probablement l\'appeler avec le nom de votre enfant.</string>
|
||||||
|
<string name="default_logbook_name">👶 Mon premier carnet de bord</string>
|
||||||
|
<string name="logbook_created">Journal ajouté: </string>
|
||||||
|
|
||||||
|
<string name="breastfeeding_timer_title">Allaitement en cours</string>
|
||||||
|
<string name="breastfeeding_timer_stop">Arrêter</string>
|
||||||
|
<string name="breastfeeding_timer_hint">Appuyez sur Arrêter quand terminé</string>
|
||||||
|
<string name="breastfeeding_timer_already_running">Une session d\'allaitement est déjà en cours</string>
|
||||||
|
<string name="breastfeeding_duration_title">Durée d\'allaitement</string>
|
||||||
|
<string name="breastfeeding_duration_description">Entrez la durée en minutes</string>
|
||||||
|
|
||||||
|
<!-- Suivi du sommeil -->
|
||||||
|
<string name="event_sleep_desc">Sommeil</string>
|
||||||
|
<string name="sleep_timer_title">Bébé dort</string>
|
||||||
|
<string name="sleep_timer_stop">Réveillé</string>
|
||||||
|
<string name="sleep_timer_hint">Appuyez quand bébé se réveille</string>
|
||||||
|
<string name="sleep_timer_already_running">Une session de sommeil est déjà en cours</string>
|
||||||
|
<string name="sleep_duration_title">Durée du sommeil</string>
|
||||||
|
<string name="sleep_duration_description">Entrez la durée en minutes</string>
|
||||||
|
<string name="overflow_event_sleep">🌙 Sommeil</string>
|
||||||
|
|
||||||
|
<!-- Statistiques -->
|
||||||
|
<string name="statistics_title">Statistiques</string>
|
||||||
|
<string name="stats_tab_today">Aujourd\'hui</string>
|
||||||
|
<string name="stats_tab_feeding">Alimentation</string>
|
||||||
|
<string name="stats_tab_diapers">Couches</string>
|
||||||
|
<string name="stats_tab_sleep">Sommeil</string>
|
||||||
|
<string name="stats_tab_growth">Croissance</string>
|
||||||
|
|
||||||
|
<string name="stats_period_7days">7 jours</string>
|
||||||
|
<string name="stats_period_14days">14 jours</string>
|
||||||
|
<string name="stats_period_30days">30 jours</string>
|
||||||
|
|
||||||
|
<string name="stats_feeding_title">Alimentation</string>
|
||||||
|
<string name="stats_sleep_title">Sommeil</string>
|
||||||
|
<string name="stats_diapers_title">Couches</string>
|
||||||
|
<string name="stats_health_title">Santé</string>
|
||||||
|
|
||||||
|
<string name="stats_today">Aujourd\'hui</string>
|
||||||
|
<string name="stats_times">%d fois</string>
|
||||||
|
<string name="stats_count_format">%d× aujourd\'hui</string>
|
||||||
|
<string name="stats_avg_format">Moy: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_daily_intake">Apport quotidien</string>
|
||||||
|
<string name="stats_bottle_title">Biberon</string>
|
||||||
|
<string name="stats_breastfeeding_title">Allaitement</string>
|
||||||
|
<string name="stats_avg_per_day">Moy: %.1f/jour</string>
|
||||||
|
<string name="stats_avg_ml_per_day">Moy: %.0f ml/jour</string>
|
||||||
|
<string name="stats_avg_min_per_day">Moy: %.0f min/jour</string>
|
||||||
|
<string name="stats_feeding_interval">Intervalle: moy. %d min</string>
|
||||||
|
<string name="stats_avg_duration">Durée moy: %.1f min</string>
|
||||||
|
<string name="stats_side_distribution">Répartition des côtés</string>
|
||||||
|
<string name="stats_left">Gauche</string>
|
||||||
|
<string name="stats_right">Droite</string>
|
||||||
|
|
||||||
|
<string name="stats_diapers_per_day">Couches par jour</string>
|
||||||
|
<string name="stats_poo">Selles</string>
|
||||||
|
<string name="stats_pee">Urine</string>
|
||||||
|
<string name="stats_summary">Résumé</string>
|
||||||
|
<string name="stats_avg_diapers">Moy: %.1f couches/jour</string>
|
||||||
|
<string name="stats_avg_poo">Selles: %.1f/jour</string>
|
||||||
|
<string name="stats_avg_pee">Urine: %.1f/jour</string>
|
||||||
|
<string name="stats_last_poo">Dernières selles: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_sleep_per_day">Sommeil par jour</string>
|
||||||
|
<string name="stats_sleep_analysis">Analyse du sommeil</string>
|
||||||
|
<string name="stats_avg_sleep">Moy: %.1f heures/jour</string>
|
||||||
|
<string name="stats_avg_naps">Moy: %.1f siestes/jour</string>
|
||||||
|
<string name="stats_avg_nap_duration">Sieste moy: %.0f min</string>
|
||||||
|
<string name="stats_longest_sleep">Plus long: %d min</string>
|
||||||
|
<string name="stats_no_sleep_data">Aucune donnée de sommeil enregistrée</string>
|
||||||
|
<string name="stats_hours_format">%.1f h</string>
|
||||||
|
|
||||||
|
<string name="stats_weight_curve">Courbe de poids</string>
|
||||||
|
<string name="stats_weight_summary">Poids</string>
|
||||||
|
<string name="stats_current_weight">Actuel: %s</string>
|
||||||
|
<string name="stats_weight_gain_week">7 derniers jours: %+d g</string>
|
||||||
|
<string name="stats_weight_gain_month">30 derniers jours: %+d g</string>
|
||||||
|
<string name="stats_no_weight_data">Aucune donnée de poids enregistrée</string>
|
||||||
|
<string name="stats_weight_format">%.2f kg</string>
|
||||||
|
<string name="stats_temperature_format">%.1f °C</string>
|
||||||
|
|
||||||
|
<!-- Export/Import -->
|
||||||
|
<string name="settings_backup_title">Sauvegarde des données</string>
|
||||||
|
<string name="settings_export">Exporter le journal</string>
|
||||||
|
<string name="settings_export_desc">Enregistrer tous les événements en fichier JSON</string>
|
||||||
|
<string name="settings_import">Importer un journal</string>
|
||||||
|
<string name="settings_import_desc">Charger les événements depuis un fichier JSON</string>
|
||||||
|
<string name="export_success">%d événements exportés</string>
|
||||||
|
<string name="export_error">Échec de l\'export: </string>
|
||||||
|
<string name="import_success">%d événements importés</string>
|
||||||
|
<string name="import_error">Échec de l\'import</string>
|
||||||
|
|
||||||
|
</resources>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">LunaTracker</string>
|
<string name="app_name">LunaTracker</string>
|
||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="log_an_event">Logga un evento:</string>
|
|
||||||
<string name="logbook">Diario di bordo</string>
|
<string name="logbook">Diario di bordo</string>
|
||||||
|
|
||||||
<string name="log_bottle_dialog_title">Biberon</string>
|
<string name="log_bottle_dialog_title">Biberon</string>
|
||||||
@@ -13,6 +12,7 @@
|
|||||||
<string name="log_temperature_dialog_title">Temperatura</string>
|
<string name="log_temperature_dialog_title">Temperatura</string>
|
||||||
<string name="log_temperature_dialog_description">Inserisci la temperatura</string>
|
<string name="log_temperature_dialog_description">Inserisci la temperatura</string>
|
||||||
|
|
||||||
|
<string name="overflow_event_scale">⚖️ Peso</string>
|
||||||
<string name="overflow_event_medicine">💊 Medicina</string>
|
<string name="overflow_event_medicine">💊 Medicina</string>
|
||||||
<string name="overflow_event_enema">🪠 Clistere</string>
|
<string name="overflow_event_enema">🪠 Clistere</string>
|
||||||
<string name="overflow_event_note">📝 Nota</string>
|
<string name="overflow_event_note">📝 Nota</string>
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
<string name="overflow_event_colic">💨 Colichette</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_food_desc">Cibo</string>
|
||||||
<string name="event_scale_desc">Pesata</string>
|
<string name="event_scale_desc">Pesata</string>
|
||||||
<string name="event_breastfeeding_left_desc">Allatt. al seno (sx)</string>
|
<string name="event_breastfeeding_left_desc">Allatt. al seno (sx)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Allatt. al seno</string>
|
<string name="event_breastfeeding_both_desc">Allatt. al seno</string>
|
||||||
@@ -61,6 +62,7 @@
|
|||||||
<string name="settings_storage_dav_pass">Password</string>
|
<string name="settings_storage_dav_pass">Password</string>
|
||||||
<string name="settings_network_error">Impossibile raggiungere il server: </string>
|
<string name="settings_network_error">Impossibile raggiungere il server: </string>
|
||||||
<string name="settings_webdav_error_denied">Nome utente o password WebDAV sbagliati</string>
|
<string name="settings_webdav_error_denied">Nome utente o password WebDAV sbagliati</string>
|
||||||
|
<string name="settings_webdav_error_server_offline">Il server WebDAV non è al momento disponibile</string>
|
||||||
<string name="settings_webdav_error_generic">Si è verificato un errore tentando di accedere al server WebDAV:</string>
|
<string name="settings_webdav_error_generic">Si è verificato un errore tentando di accedere al server WebDAV:</string>
|
||||||
<string name="settings_webdav_creation_error_generic">Impossibile creare un file di salvataggio sul server WebDAV:</string>
|
<string name="settings_webdav_creation_error_generic">Impossibile creare un file di salvataggio sul server WebDAV:</string>
|
||||||
<string name="settings_webdav_creation_ok">Connessione al server WebDAV avvenuta con successo</string>
|
<string name="settings_webdav_creation_ok">Connessione al server WebDAV avvenuta con successo</string>
|
||||||
@@ -80,12 +82,104 @@
|
|||||||
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
|
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Dettaglio evento</string>
|
<string name="dialog_event_detail_title">Dettaglio evento</string>
|
||||||
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
<string name="dialog_event_detail_delete_button">Elimina</string>
|
||||||
|
|
||||||
<string name="dialog_add_logbook_title">Aggiungi diario</string>
|
<string name="dialog_add_logbook_title">Aggiungi diario</string>
|
||||||
<string name="dialog_add_logbook_logbookname">👶 Nome del diario</string>
|
<string name="dialog_add_logbook_logbookname">👶 Nome del diario</string>
|
||||||
<string name="dialog_add_logbook_message">Scrivi un nome per identificare questo diario. Comparirà in cima allo schermo, e se usi WebDAV sarà incluso anche nel nome del file di salvataggio.\nSe vuoi un\'icona, inserisci una emoji!</string>
|
<string name="dialog_add_logbook_message">Scrivi un nome per identificare questo diario. Comparirà in cima allo schermo, e se usi WebDAV sarà incluso anche nel nome del file di salvataggio.\nSe vuoi un\'icona, inserisci una emoji!</string>
|
||||||
|
|
||||||
|
<string name="dialog_add_logbook_message_intro">Benvenuto! Per usare quest\'app devi creare almeno un diario. Probabilmente vuoi chiamarlo col nome di tuo figlio.</string>
|
||||||
<string name="default_logbook_name">👶 Il mio primo diario</string>
|
<string name="default_logbook_name">👶 Il mio primo diario</string>
|
||||||
<string name="logbook_created">Creato nuovo diario: </string>
|
<string name="logbook_created">Creato nuovo diario: </string>
|
||||||
|
|
||||||
|
<string name="breastfeeding_timer_title">Allattamento in corso</string>
|
||||||
|
<string name="breastfeeding_timer_stop">Stop</string>
|
||||||
|
<string name="breastfeeding_timer_hint">Premi Stop quando hai finito</string>
|
||||||
|
<string name="breastfeeding_timer_already_running">Una sessione di allattamento è già in corso</string>
|
||||||
|
<string name="breastfeeding_duration_title">Durata allattamento</string>
|
||||||
|
<string name="breastfeeding_duration_description">Inserisci la durata in minuti</string>
|
||||||
|
|
||||||
|
<!-- Tracciamento del sonno -->
|
||||||
|
<string name="event_sleep_desc">Sonno</string>
|
||||||
|
<string name="sleep_timer_title">Il bimbo dorme</string>
|
||||||
|
<string name="sleep_timer_stop">Svegliato</string>
|
||||||
|
<string name="sleep_timer_hint">Premi quando il bimbo si sveglia</string>
|
||||||
|
<string name="sleep_timer_already_running">Una sessione di sonno è già in corso</string>
|
||||||
|
<string name="sleep_duration_title">Durata del sonno</string>
|
||||||
|
<string name="sleep_duration_description">Inserisci la durata in minuti</string>
|
||||||
|
<string name="overflow_event_sleep">🌙 Sonno</string>
|
||||||
|
|
||||||
|
<!-- Statistiche -->
|
||||||
|
<string name="statistics_title">Statistiche</string>
|
||||||
|
<string name="stats_tab_today">Oggi</string>
|
||||||
|
<string name="stats_tab_feeding">Alimentazione</string>
|
||||||
|
<string name="stats_tab_diapers">Pannolini</string>
|
||||||
|
<string name="stats_tab_sleep">Sonno</string>
|
||||||
|
<string name="stats_tab_growth">Crescita</string>
|
||||||
|
|
||||||
|
<string name="stats_period_7days">7 giorni</string>
|
||||||
|
<string name="stats_period_14days">14 giorni</string>
|
||||||
|
<string name="stats_period_30days">30 giorni</string>
|
||||||
|
|
||||||
|
<string name="stats_feeding_title">Alimentazione</string>
|
||||||
|
<string name="stats_sleep_title">Sonno</string>
|
||||||
|
<string name="stats_diapers_title">Pannolini</string>
|
||||||
|
<string name="stats_health_title">Salute</string>
|
||||||
|
|
||||||
|
<string name="stats_today">Oggi</string>
|
||||||
|
<string name="stats_times">%d volte</string>
|
||||||
|
<string name="stats_count_format">%d× oggi</string>
|
||||||
|
<string name="stats_avg_format">Media: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_daily_intake">Assunzione giornaliera</string>
|
||||||
|
<string name="stats_bottle_title">Biberon</string>
|
||||||
|
<string name="stats_breastfeeding_title">Allattamento</string>
|
||||||
|
<string name="stats_avg_per_day">Media: %.1f/giorno</string>
|
||||||
|
<string name="stats_avg_ml_per_day">Media: %.0f ml/giorno</string>
|
||||||
|
<string name="stats_avg_min_per_day">Media: %.0f min/giorno</string>
|
||||||
|
<string name="stats_feeding_interval">Intervallo: media %d min</string>
|
||||||
|
<string name="stats_avg_duration">Durata media: %.1f min</string>
|
||||||
|
<string name="stats_side_distribution">Distribuzione lati</string>
|
||||||
|
<string name="stats_left">Sinistra</string>
|
||||||
|
<string name="stats_right">Destra</string>
|
||||||
|
|
||||||
|
<string name="stats_diapers_per_day">Pannolini al giorno</string>
|
||||||
|
<string name="stats_poo">Cacca</string>
|
||||||
|
<string name="stats_pee">Pipì</string>
|
||||||
|
<string name="stats_summary">Riepilogo</string>
|
||||||
|
<string name="stats_avg_diapers">Media: %.1f pannolini/giorno</string>
|
||||||
|
<string name="stats_avg_poo">Cacca: %.1f/giorno</string>
|
||||||
|
<string name="stats_avg_pee">Pipì: %.1f/giorno</string>
|
||||||
|
<string name="stats_last_poo">Ultima cacca: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_sleep_per_day">Sonno al giorno</string>
|
||||||
|
<string name="stats_sleep_analysis">Analisi del sonno</string>
|
||||||
|
<string name="stats_avg_sleep">Media: %.1f ore/giorno</string>
|
||||||
|
<string name="stats_avg_naps">Media: %.1f sonnellini/giorno</string>
|
||||||
|
<string name="stats_avg_nap_duration">Sonnellino medio: %.0f min</string>
|
||||||
|
<string name="stats_longest_sleep">Più lungo: %d min</string>
|
||||||
|
<string name="stats_no_sleep_data">Nessun dato sul sonno registrato</string>
|
||||||
|
<string name="stats_hours_format">%.1f h</string>
|
||||||
|
|
||||||
|
<string name="stats_weight_curve">Curva di peso</string>
|
||||||
|
<string name="stats_weight_summary">Peso</string>
|
||||||
|
<string name="stats_current_weight">Attuale: %s</string>
|
||||||
|
<string name="stats_weight_gain_week">Ultimi 7 giorni: %+d g</string>
|
||||||
|
<string name="stats_weight_gain_month">Ultimi 30 giorni: %+d g</string>
|
||||||
|
<string name="stats_no_weight_data">Nessun dato sul peso registrato</string>
|
||||||
|
<string name="stats_weight_format">%.2f kg</string>
|
||||||
|
<string name="stats_temperature_format">%.1f °C</string>
|
||||||
|
|
||||||
|
<!-- Export/Import -->
|
||||||
|
<string name="settings_backup_title">Backup dati</string>
|
||||||
|
<string name="settings_export">Esporta diario</string>
|
||||||
|
<string name="settings_export_desc">Salva tutti gli eventi come file JSON</string>
|
||||||
|
<string name="settings_import">Importa diario</string>
|
||||||
|
<string name="settings_import_desc">Carica eventi da file JSON</string>
|
||||||
|
<string name="export_success">%d eventi esportati</string>
|
||||||
|
<string name="export_error">Esportazione fallita: </string>
|
||||||
|
<string name="import_success">%d eventi importati</string>
|
||||||
|
<string name="import_error">Importazione fallita</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
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>
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">LunaTracker</string>
|
<string name="app_name">LunaTracker</string>
|
||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="log_an_event">Log an event:</string>
|
|
||||||
<string name="logbook">Logged events</string>
|
<string name="logbook">Logged events</string>
|
||||||
|
|
||||||
<string name="log_bottle_dialog_title">Baby bottle</string>
|
<string name="log_bottle_dialog_title">Baby bottle</string>
|
||||||
@@ -13,7 +12,11 @@
|
|||||||
<string name="log_temperature_dialog_title">Temperature</string>
|
<string name="log_temperature_dialog_title">Temperature</string>
|
||||||
<string name="log_temperature_dialog_description">Insert the 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_bottle_type" translatable="false">🍼</string>
|
||||||
|
<string name="event_food_type" translatable="false">🥣</string>
|
||||||
<string name="event_scale_type" translatable="false">⚖️</string>
|
<string name="event_scale_type" translatable="false">⚖️</string>
|
||||||
<string name="event_breastfeeding_left_type" translatable="false">🤱 ←</string>
|
<string name="event_breastfeeding_left_type" translatable="false">🤱 ←</string>
|
||||||
<string name="event_breastfeeding_both_type" translatable="false">🤱 ↔</string>
|
<string name="event_breastfeeding_both_type" translatable="false">🤱 ↔</string>
|
||||||
@@ -25,9 +28,13 @@
|
|||||||
<string name="event_note_type" translatable="false">📝</string>
|
<string name="event_note_type" translatable="false">📝</string>
|
||||||
<string name="event_temperature_type" translatable="false">🌡️</string>
|
<string name="event_temperature_type" translatable="false">🌡️</string>
|
||||||
<string name="event_colic_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_sleep_type" translatable="false">🌙</string>
|
||||||
<string name="event_unknown_type" translatable="false">\?</string>
|
<string name="event_unknown_type" translatable="false">\?</string>
|
||||||
|
|
||||||
<string name="event_bottle_desc">Baby bottle</string>
|
<string name="event_bottle_desc">Baby bottle</string>
|
||||||
|
<string name="event_food_desc">Food</string>
|
||||||
<string name="event_scale_desc">Weight</string>
|
<string name="event_scale_desc">Weight</string>
|
||||||
<string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
|
<string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Breastfeeding</string>
|
<string name="event_breastfeeding_both_desc">Breastfeeding</string>
|
||||||
@@ -39,13 +46,19 @@
|
|||||||
<string name="event_note_desc">Note</string>
|
<string name="event_note_desc">Note</string>
|
||||||
<string name="event_temperature_desc">Temperature</string>
|
<string name="event_temperature_desc">Temperature</string>
|
||||||
<string name="event_colic_desc">Gaseous colic</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_sleep_desc">Sleep</string>
|
||||||
<string name="event_unknown_desc"></string>
|
<string name="event_unknown_desc"></string>
|
||||||
|
|
||||||
|
<string name="overflow_event_scale">⚖️ Weight</string>
|
||||||
<string name="overflow_event_medicine">💊 Medicine</string>
|
<string name="overflow_event_medicine">💊 Medicine</string>
|
||||||
<string name="overflow_event_enema">🪠 Enema</string>
|
<string name="overflow_event_enema">🪠 Enema</string>
|
||||||
<string name="overflow_event_note">📝 Note</string>
|
<string name="overflow_event_note">📝 Note</string>
|
||||||
<string name="overflow_event_temperature">🌡️ Temperature</string>
|
<string name="overflow_event_temperature">🌡️ Temperature</string>
|
||||||
<string name="overflow_event_colic">💨 Gaseous colic</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_event_added">Event logged</string>
|
||||||
<string name="toast_logbook_saved">Logbook saved</string>
|
<string name="toast_logbook_saved">Logbook saved</string>
|
||||||
@@ -53,10 +66,20 @@
|
|||||||
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
|
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
|
||||||
|
|
||||||
<string name="now">now</string>
|
<string name="now">now</string>
|
||||||
<string name="hour_ago">hour</string>
|
<string name="second_ago">sec</string>
|
||||||
<string name="hours_ago">hours</string>
|
<string name="seconds_ago">secs</string>
|
||||||
<string name="minute_ago">min</string>
|
<string name="minute_ago">min</string>
|
||||||
<string name="minutes_ago">mins</string>
|
<string name="minutes_ago">mins</string>
|
||||||
|
<string name="hour_ago">hour</string>
|
||||||
|
<string name="hours_ago">hours</string>
|
||||||
|
<string name="day_ago">day</string>
|
||||||
|
<string name="days_ago">days</string>
|
||||||
|
<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">No connection</string>
|
||||||
<string name="no_connection_explain">Unable to reach WebDAV service</string>
|
<string name="no_connection_explain">Unable to reach WebDAV service</string>
|
||||||
@@ -64,27 +87,32 @@
|
|||||||
<string name="no_connection_retry">Retry</string>
|
<string name="no_connection_retry">Retry</string>
|
||||||
|
|
||||||
<string name="settings_title">Settings</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">Choose where to save data</string>
|
||||||
<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>
|
||||||
<string name="settings_storage_dav_pass">Password</string>
|
<string name="settings_storage_dav_pass">Password</string>
|
||||||
<string name="settings_network_error">Unable to reach server: </string>
|
<string name="settings_network_error">Unable to reach server: </string>
|
||||||
<string name="settings_webdav_error_denied">Wrong WebDAV user or password</string>
|
<string name="settings_webdav_error_denied">Wrong WebDAV user or password</string>
|
||||||
|
<string name="settings_webdav_error_server_offline">WebDAV server is currently unavailable</string>
|
||||||
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
|
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
|
||||||
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
|
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
|
||||||
<string name="settings_webdav_creation_ok">Successfully connected with WebDAV server</string>
|
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
|
||||||
<string name="settings_json_error">There\'s a save file on the server, but is corrupted or unreadable. Please delete it </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_generic_error">Error: </string>
|
||||||
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
|
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
|
||||||
|
|
||||||
<string name="trim_logbook_dialog_title">Your logbook is pretty big!</string>
|
<string name="trim_logbook_dialog_title">Your logbook is pretty big!</string>
|
||||||
<string name="trim_logbook_dialog_message_local">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes.</string>
|
<string name="trim_logbook_dialog_message_local">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes.</string>
|
||||||
<string name="trim_logbook_dialog_message_dav">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes. If you want to preserve all the events, please backup the "lunatracker_logbook.json" file on the WebDAV server or rename it to start a new logbook keeping the old one.</string>
|
<string name="trim_logbook_dialog_message_dav">Your logbook file is growing a lot. We suggest trimming the oldest events to avoid crashes. If you want to preserve all the events, please back up the "lunatracker_logbook.json" file on the WebDAV server or rename it to start a new logbook, keeping the old one.</string>
|
||||||
<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>
|
||||||
|
|
||||||
@@ -102,14 +130,113 @@
|
|||||||
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</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="measurement_unit_temperature_base_metric" translatable="false">°C</string>
|
||||||
|
|
||||||
|
<string name="row_luna_event_description">Description</string>
|
||||||
|
<string name="row_luna_event_quantity">Qty</string>
|
||||||
|
<string name="row_luna_event_time">Time</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Event detail</string>
|
<string name="dialog_event_detail_title">Event detail</string>
|
||||||
|
<string name="dialog_event_detail_datetime_icon" translatable="false">🕒 %s</string>
|
||||||
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
<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_title">Add logbook</string>
|
||||||
<string name="dialog_add_logbook_logbookname">👶 Logbook name</string>
|
<string name="dialog_add_logbook_logbookname">👶 Logbook name</string>
|
||||||
<string name="dialog_add_logbook_message">Write a name to identify this logbook. This name will appear on top of the screen and, if you use WebDAV, will be in the save file name as well.</string>
|
<string name="dialog_add_logbook_message">Write a name to identify this logbook. This name will appear on top of the screen and, if you use WebDAV, will be in the save file name as well.</string>
|
||||||
<string name="dialog_add_logbook_message_intro">Welcome! To use this app you need to create at least one logbook. You would probably want to call it with your child\'s name.</string>
|
<string name="dialog_add_logbook_message_intro">Welcome! To use this app, you need to create at least one logbook. You would probably want to call it with your child\'s name.</string>
|
||||||
|
|
||||||
<string name="default_logbook_name">👶 My first logbook</string>
|
<string name="default_logbook_name">👶 My first logbook</string>
|
||||||
<string name="logbook_created">New logbook created: </string>
|
<string name="logbook_created">New logbook created: </string>
|
||||||
|
|
||||||
|
<string name="breastfeeding_timer_title">Breastfeeding in progress</string>
|
||||||
|
<string name="breastfeeding_timer_stop">Stop</string>
|
||||||
|
<string name="breastfeeding_timer_hint">Tap Stop when finished</string>
|
||||||
|
<string name="breastfeeding_timer_already_running">A breastfeeding session is already in progress</string>
|
||||||
|
<string name="breastfeeding_duration_title">Breastfeeding duration</string>
|
||||||
|
<string name="breastfeeding_duration_description">Enter the duration in minutes</string>
|
||||||
|
<string name="measurement_unit_time_minutes" translatable="false">min</string>
|
||||||
|
|
||||||
|
<!-- Sleep tracking -->
|
||||||
|
<string name="sleep_timer_title">Baby is sleeping</string>
|
||||||
|
<string name="sleep_timer_stop">Woke up</string>
|
||||||
|
<string name="sleep_timer_hint">Tap when baby wakes up</string>
|
||||||
|
<string name="sleep_timer_already_running">A sleep session is already in progress</string>
|
||||||
|
<string name="sleep_duration_title">Sleep duration</string>
|
||||||
|
<string name="sleep_duration_description">Enter the duration in minutes</string>
|
||||||
|
<string name="overflow_event_sleep">🌙 Sleep</string>
|
||||||
|
|
||||||
|
<!-- Statistics -->
|
||||||
|
<string name="statistics_title">Statistics</string>
|
||||||
|
<string name="stats_tab_today">Today</string>
|
||||||
|
<string name="stats_tab_feeding">Feeding</string>
|
||||||
|
<string name="stats_tab_diapers">Diapers</string>
|
||||||
|
<string name="stats_tab_sleep">Sleep</string>
|
||||||
|
<string name="stats_tab_growth">Growth</string>
|
||||||
|
|
||||||
|
<string name="stats_period_7days">7 days</string>
|
||||||
|
<string name="stats_period_14days">14 days</string>
|
||||||
|
<string name="stats_period_30days">30 days</string>
|
||||||
|
|
||||||
|
<string name="stats_feeding_title">Feeding</string>
|
||||||
|
<string name="stats_sleep_title">Sleep</string>
|
||||||
|
<string name="stats_diapers_title">Diapers</string>
|
||||||
|
<string name="stats_health_title">Health</string>
|
||||||
|
|
||||||
|
<string name="stats_today">Today</string>
|
||||||
|
<string name="stats_times">%d times</string>
|
||||||
|
<string name="stats_count_format">%d× today</string>
|
||||||
|
<string name="stats_avg_format">Avg: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_daily_intake">Daily intake</string>
|
||||||
|
<string name="stats_bottle_title">Bottle</string>
|
||||||
|
<string name="stats_breastfeeding_title">Breastfeeding</string>
|
||||||
|
<string name="stats_avg_per_day">Avg: %.1f/day</string>
|
||||||
|
<string name="stats_avg_ml_per_day">Avg: %.0f ml/day</string>
|
||||||
|
<string name="stats_avg_min_per_day">Avg: %.0f min/day</string>
|
||||||
|
<string name="stats_feeding_interval">Feeding interval: avg. %d min</string>
|
||||||
|
<string name="stats_avg_duration">Avg duration: %.1f min</string>
|
||||||
|
<string name="stats_side_distribution">Side distribution</string>
|
||||||
|
<string name="stats_left">Left</string>
|
||||||
|
<string name="stats_right">Right</string>
|
||||||
|
|
||||||
|
<string name="stats_diapers_per_day">Diapers per day</string>
|
||||||
|
<string name="stats_poo">Poo</string>
|
||||||
|
<string name="stats_pee">Pee</string>
|
||||||
|
<string name="stats_summary">Summary</string>
|
||||||
|
<string name="stats_avg_diapers">Avg: %.1f diapers/day</string>
|
||||||
|
<string name="stats_avg_poo">Poo: %.1f/day</string>
|
||||||
|
<string name="stats_avg_pee">Pee: %.1f/day</string>
|
||||||
|
<string name="stats_last_poo">Last poo: %s</string>
|
||||||
|
|
||||||
|
<string name="stats_sleep_per_day">Sleep per day</string>
|
||||||
|
<string name="stats_sleep_analysis">Sleep analysis</string>
|
||||||
|
<string name="stats_avg_sleep">Avg: %.1f hours/day</string>
|
||||||
|
<string name="stats_avg_naps">Avg: %.1f naps/day</string>
|
||||||
|
<string name="stats_avg_nap_duration">Avg nap: %.0f min</string>
|
||||||
|
<string name="stats_longest_sleep">Longest: %d min</string>
|
||||||
|
<string name="stats_no_sleep_data">No sleep data recorded yet</string>
|
||||||
|
<string name="stats_hours_format">%.1f h</string>
|
||||||
|
|
||||||
|
<string name="stats_weight_curve">Weight curve</string>
|
||||||
|
<string name="stats_weight_summary">Weight</string>
|
||||||
|
<string name="stats_current_weight">Current: %s</string>
|
||||||
|
<string name="stats_weight_gain_week">Last 7 days: %+d g</string>
|
||||||
|
<string name="stats_weight_gain_month">Last 30 days: %+d g</string>
|
||||||
|
<string name="stats_no_weight_data">No weight data recorded yet</string>
|
||||||
|
<string name="stats_weight_format">%.2f kg</string>
|
||||||
|
<string name="stats_temperature_format">%.1f °C</string>
|
||||||
|
|
||||||
|
<!-- Export/Import -->
|
||||||
|
<string name="settings_backup_title">Data Backup</string>
|
||||||
|
<string name="settings_export">Export Logbook</string>
|
||||||
|
<string name="settings_export_desc">Save all events as JSON file</string>
|
||||||
|
<string name="settings_import">Import Logbook</string>
|
||||||
|
<string name="settings_import_desc">Load events from JSON file</string>
|
||||||
|
<string name="export_success">Exported %d events</string>
|
||||||
|
<string name="export_error">Export failed: </string>
|
||||||
|
<string name="import_success">Imported %d events</string>
|
||||||
|
<string name="import_error">Import failed</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
3
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
3
fastlane/metadata/android/en-US/changelogs/3.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
Multiple children support
|
||||||
|
Fixed interface in devices with big font size
|
||||||
|
|
||||||
@@ -1,11 +1,12 @@
|
|||||||
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, even 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 of the reason behind the cries.
|
||||||
|
|
||||||
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, etc.), so you can always remember what was done last time and when. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attention, the dad can step in, leaving the mom sleeping peacefully.
|
||||||
|
|
||||||
Dedicated to my daughter Luna.
|
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.
|
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
|
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
fastlane/metadata/android/fr-FR/changelogs/1.txt
Normal file
1
fastlane/metadata/android/fr-FR/changelogs/1.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Première version alpha : carnet de bord, synchronisation.
|
||||||
2
fastlane/metadata/android/fr-FR/changelogs/3.txt
Normal file
2
fastlane/metadata/android/fr-FR/changelogs/3.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
Prise en charge de plusieurs enfants
|
||||||
|
Correction de l'interface sur les appareils avec une grande taille de police.
|
||||||
15
fastlane/metadata/android/fr-FR/full_description.txt
Normal file
15
fastlane/metadata/android/fr-FR/full_description.txt
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
Être parent n'est pas toujours facile. Vous rentrez de la maternité, épuisé, avec cette petite créature inconnue et fragile qui n'a pas de mode d'emploi et une seule façon de vous faire savoir que quelque chose ne va pas : les pleurs.
|
||||||
|
Vous devez réagir rapidement, même s'il est 4 heures du matin et que vous n'avez aucune idée de la raison de ses pleurs.
|
||||||
|
|
||||||
|
Cette application est destinée à enregistrer tous les événements importants (changement de couche, allaitement, biberon...), afin que vous puissiez toujours vous souvenir de ce qui a été fait la dernière fois (et quand).
|
||||||
|
|
||||||
|
Elle permet de synchroniser les données entre différents appareils (en utilisant votre serveur WebDAV pour une meilleure confidentialité). Ainsi, lorsque le bébé a besoin d'attention, le papa peut intervenir et laisser la maman dormir tranquillement.
|
||||||
|
|
||||||
|
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
|
||||||
1
fastlane/metadata/android/fr-FR/short_description.txt
Normal file
1
fastlane/metadata/android/fr-FR/short_description.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
LunaTracker est une application de suivi des nouveau-nés.
|
||||||
1
fastlane/metadata/android/fr-FR/title.txt
Normal file
1
fastlane/metadata/android/fr-FR/title.txt
Normal file
@@ -0,0 +1 @@
|
|||||||
|
🌜 LunaTracker 🌛
|
||||||
@@ -6,6 +6,7 @@ Quest'app è pensata per memorizzare tutti gli eventi del bambino (cambio di pan
|
|||||||
Dedicato a mia figlia Luna.
|
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.
|
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
|
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]
|
[versions]
|
||||||
agp = "8.7.2"
|
agp = "8.13.0"
|
||||||
kotlin = "2.0.0"
|
kotlin = "2.0.0"
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
junit = "4.13.2"
|
junit = "4.13.2"
|
||||||
|
|||||||
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
|
#Sat Nov 02 10:58:51 CET 2024
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
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
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Reference in New Issue
Block a user