forked from penguin86/luna-tracker
Compare commits
81 Commits
v0.2
...
b6110c2cbb
| Author | SHA1 | Date | |
|---|---|---|---|
| b6110c2cbb | |||
| dccc89a8e2 | |||
| 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 | |||
| 99743288c5 | |||
| 03ec28f8ef | |||
| ac9f74dbd7 | |||
| 36f52234a3 | |||
| e23ab77274 | |||
| e3aceaf133 | |||
| 0494a11538 | |||
| ecbde64ca1 | |||
| 32fbeac079 | |||
| 0b0fd8f5af | |||
| fe5da015cb | |||
| b1e26f8a96 | |||
| 48017f70f6 | |||
| 524a121149 | |||
| e1388f9bad | |||
| 90761afef2 | |||
| b02a6c9aad | |||
| 0d331d90ff | |||
| 5d17ebf46b | |||
| 3cbc4139ae | |||
| f31c68193a | |||
| de5b1d4ead | |||
| de75ed584b | |||
| a4b43ffb10 | |||
| 37b68450da | |||
| 841da9e28e | |||
| 876c68ce81 | |||
| 35a3b42a32 | |||
| d5cb30cdd7 | |||
| 57f997e6f1 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -106,3 +106,6 @@ app/release/output-metadata.json
|
||||
|
||||
# Other
|
||||
app/src/main/java/it/danieleverducci/lunatracker/TemporaryHardcodedCredentials.kt
|
||||
.kotlin/sessions/*
|
||||
CLAUDE.md
|
||||
.claude/
|
||||
|
||||
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>
|
||||
12
README.md
12
README.md
@@ -1,10 +1,20 @@
|
||||
# 🌜 LunaTracker 🌛
|
||||
|
||||
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/it/packages/it.danieleverducci.lunatracker/)
|
||||
|
||||
LunaTracker is a newborn baby tracking app.
|
||||
|
||||
Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying.
|
||||
You have to react fast, event if it's 4 AM and you have no idea why is crying.
|
||||
You have to react fast, even if it's 4 AM and you have no idea why is crying.
|
||||
|
||||
This app is meant to log all the relevant events (diaper change, breastfeeding, baby bottle feeding...), so you can always remember when has been done last time. It supports syncing the data between different devices (using your WebDAV server for the best privacy) so that when the baby needs attentions, the dad can step in leaving the mom sleeping peacefully.
|
||||
|
||||
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)
|
||||
|
||||
@@ -10,10 +10,10 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "it.danieleverducci.lunatracker"
|
||||
minSdk = 29
|
||||
minSdk = 21
|
||||
targetSdk = 34
|
||||
versionCode = 1
|
||||
versionName = "0.2"
|
||||
versionCode = 5
|
||||
versionName = "0.7"
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@@ -30,6 +30,10 @@
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/settings_title"
|
||||
android:theme="@style/Theme.LunaTracker"/>
|
||||
<activity
|
||||
android:name=".StatisticsActivity"
|
||||
android:label="@string/statistics_title"
|
||||
android:theme="@style/Theme.LunaTracker"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,17 +1,26 @@
|
||||
package it.danieleverducci.lunatracker
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
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.LocalSettingsRepository
|
||||
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
|
||||
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
|
||||
import okio.IOException
|
||||
import org.json.JSONException
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
open class SettingsActivity : AppCompatActivity() {
|
||||
protected lateinit var settingsRepository: LocalSettingsRepository
|
||||
@@ -21,6 +30,17 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
protected lateinit var textViewWebDAVUser: TextView
|
||||
protected lateinit var textViewWebDAVPass: TextView
|
||||
protected lateinit var progressIndicator: LinearProgressIndicator
|
||||
protected lateinit var switchNoBreastfeeding: SwitchMaterial
|
||||
protected lateinit var 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?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@@ -32,12 +52,21 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
|
||||
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
|
||||
progressIndicator = findViewById(R.id.progress_indicator)
|
||||
switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
|
||||
textViewSignature = findViewById(R.id.settings_signature)
|
||||
|
||||
findViewById<View>(R.id.settings_save).setOnClickListener({
|
||||
validateAndSave()
|
||||
})
|
||||
findViewById<View>(R.id.settings_cancel).setOnClickListener({
|
||||
finish()
|
||||
})
|
||||
findViewById<View>(R.id.settings_export).setOnClickListener({
|
||||
startExport()
|
||||
})
|
||||
findViewById<View>(R.id.settings_import).setOnClickListener({
|
||||
startImport()
|
||||
})
|
||||
|
||||
settingsRepository = LocalSettingsRepository(this)
|
||||
loadSettings()
|
||||
@@ -46,15 +75,21 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
fun loadSettings() {
|
||||
val dataRepo = settingsRepository.loadDataRepository()
|
||||
val webDavCredentials = settingsRepository.loadWebdavCredentials()
|
||||
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
|
||||
val signature = settingsRepository.loadSignature()
|
||||
|
||||
when (dataRepo) {
|
||||
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
|
||||
LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
|
||||
}
|
||||
|
||||
textViewSignature.setText(signature)
|
||||
switchNoBreastfeeding.isChecked = noBreastfeeding
|
||||
|
||||
if (webDavCredentials != null) {
|
||||
textViewWebDAVUrl.setText(webDavCredentials[0])
|
||||
textViewWebDAVUser.setText(webDavCredentials[1])
|
||||
textViewWebDAVPass.setText(webDavCredentials[2])
|
||||
textViewWebDAVUrl.text = webDavCredentials[0]
|
||||
textViewWebDAVUser.text = webDavCredentials[1]
|
||||
textViewWebDAVPass.text = webDavCredentials[2]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +107,34 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
textViewWebDAVPass.text.toString()
|
||||
)
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
webDAVLogbookRepo.createLogbook(this, object: WebDAVLogbookRepository.LogbookCreatedListener{
|
||||
override fun onLogbookCreated() {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
saveSettings()
|
||||
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
|
||||
webDAVLogbookRepo.listLogbooks(this, object: LogbookListObtainedListener{
|
||||
|
||||
override fun onLogbookListObtained(logbooksNames: ArrayList<String>) {
|
||||
if (logbooksNames.isEmpty()) {
|
||||
// TODO: Ask the user if he wants to upload the local ones or to create a new one
|
||||
copyLocalLogbooksToWebdav(webDAVLogbookRepo, object: OnCopyLocalLogbooksToWebdavFinishedListener {
|
||||
|
||||
override fun onCopyLocalLogbooksToWebdavFinished(errors: String?) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
if (errors == null) {
|
||||
saveSettings()
|
||||
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
Toast.makeText(this@SettingsActivity, errors, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
} else {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
saveSettings()
|
||||
Toast.makeText(this@SettingsActivity, R.string.settings_webdav_creation_ok, Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIOError(error: IOException) {
|
||||
@@ -99,6 +155,16 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(error: Exception) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
/*webDAVLogbookRepo.createLogbook(this, LogbookRepository.DEFAULT_LOGBOOK_NAME, object: WebDAVLogbookRepository.LogbookCreatedListener{
|
||||
|
||||
override fun onJSONError(error: JSONException) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
@@ -106,14 +172,8 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(error: Exception) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(this@SettingsActivity, getString(R.string.settings_generic_error) + error.toString(), Toast.LENGTH_SHORT).show()
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
})*/
|
||||
}
|
||||
|
||||
fun saveSettings() {
|
||||
@@ -121,6 +181,8 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
|
||||
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
|
||||
)
|
||||
settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
|
||||
settingsRepository.saveSignature(textViewSignature.text.toString())
|
||||
settingsRepository.saveWebdavCredentials(
|
||||
textViewWebDAVUrl.text.toString(),
|
||||
textViewWebDAVUser.text.toString(),
|
||||
@@ -129,4 +191,164 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
finish()
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the local logbooks to webdav.
|
||||
* @return success
|
||||
*/
|
||||
private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
|
||||
Thread(Runnable {
|
||||
val errors = StringBuilder()
|
||||
val fileLogbookRepo = FileLogbookRepository()
|
||||
val logbooks = fileLogbookRepo.getAllLogbooks(this)
|
||||
for (logbook in logbooks) {
|
||||
// Copy only if does not already exist
|
||||
val error = webDAVLogbookRepository.uploadLogbookIfNotExists(this, logbook.name)
|
||||
if (error != null) {
|
||||
if (errors.isNotEmpty())
|
||||
errors.append("\n")
|
||||
errors.append(String.format(getString(R.string.settings_webdav_upload_error), logbook.name, error))
|
||||
}
|
||||
}
|
||||
listener.onCopyLocalLogbooksToWebdavFinished(
|
||||
if (errors.isEmpty()) null else errors.toString()
|
||||
)
|
||||
}).start()
|
||||
}
|
||||
|
||||
private interface OnCopyLocalLogbooksToWebdavFinishedListener {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,43 @@
|
||||
package it.danieleverducci.lunatracker.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateFormat
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import it.danieleverducci.lunatracker.R
|
||||
import java.util.Date
|
||||
import utils.DateUtils
|
||||
import utils.NumericUtils
|
||||
|
||||
class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.LunaEventVH> {
|
||||
private val context: Context
|
||||
val items = ArrayList<LunaEvent>()
|
||||
private val items: ArrayList<LunaEvent>
|
||||
val numericUtils: NumericUtils
|
||||
var onItemClickListener: OnItemClickListener? = null
|
||||
val layoutRes: Int
|
||||
|
||||
constructor(context: Context) {
|
||||
constructor(context: Context, items: ArrayList<LunaEvent>) {
|
||||
this.context = context
|
||||
this.items = items
|
||||
this.numericUtils = NumericUtils(context)
|
||||
|
||||
val fontScale = context.resources.configuration.fontScale
|
||||
val screenSize = context.resources.configuration.screenWidthDp
|
||||
this.layoutRes =
|
||||
if(fontScale > 1.2 || screenSize < 320 || (fontScale > 1 && screenSize < 400))
|
||||
R.layout.row_luna_event_vertical
|
||||
else
|
||||
R.layout.row_luna_event
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(
|
||||
parent: ViewGroup,
|
||||
viewType: Int
|
||||
): LunaEventVH {
|
||||
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.row_luna_event, parent, false)
|
||||
val view = LayoutInflater.from(context).inflate(layoutRes, parent, false)
|
||||
return LunaEventVH(view)
|
||||
}
|
||||
|
||||
@@ -33,86 +46,78 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
|
||||
position: Int
|
||||
) {
|
||||
val item = items.get(position)
|
||||
holder.type.text = context.getString(
|
||||
when (item.type) {
|
||||
LunaEvent.TYPE_BABY_BOTTLE -> R.string.event_bottle_type
|
||||
LunaEvent.TYPE_WEIGHT -> R.string.event_scale_type
|
||||
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
|
||||
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
|
||||
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
|
||||
LunaEvent.TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
|
||||
LunaEvent.TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
|
||||
else -> R.string.event_unknown_type
|
||||
}
|
||||
// Colors
|
||||
holder.root.setBackgroundResource(
|
||||
if (position % 2 == 0) R.color.list_background_even else R.color.list_background_odd
|
||||
)
|
||||
holder.description.text = context.getString(
|
||||
when (item.type) {
|
||||
LunaEvent.TYPE_BABY_BOTTLE -> R.string.event_bottle_desc
|
||||
LunaEvent.TYPE_WEIGHT -> R.string.event_scale_desc
|
||||
LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
|
||||
LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
|
||||
LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
|
||||
LunaEvent.TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc
|
||||
LunaEvent.TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc
|
||||
else -> R.string.event_unknown_desc
|
||||
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
|
||||
// Contents
|
||||
holder.type.text = item.getTypeEmoji(context)
|
||||
holder.description.text = when (item.type) {
|
||||
LunaEvent.TYPE_MEDICINE -> item.notes
|
||||
LunaEvent.TYPE_NOTE -> item.notes
|
||||
LunaEvent.TYPE_CUSTOM -> item.notes
|
||||
else -> item.getTypeDescription(context)
|
||||
}
|
||||
holder.time.text = DateUtils.formatTimeAgo(context, item.time)
|
||||
var quantityText = numericUtils.formatEventQuantity(item)
|
||||
|
||||
// if the event is weight, show difference with the last one
|
||||
if (item.type == LunaEvent.TYPE_WEIGHT) {
|
||||
val lastWeight = getPreviousWeightEvent(position)
|
||||
if (lastWeight != null) {
|
||||
val differenceInWeight = item.quantity - lastWeight.quantity
|
||||
val sign = if (differenceInWeight > 0) "+" else ""
|
||||
quantityText += "\n($sign$differenceInWeight)"
|
||||
if (differenceInWeight < 0) {
|
||||
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.danger))
|
||||
}
|
||||
}
|
||||
)
|
||||
holder.quantity.text = if ((item.quantity ?: 0) > 0) item.quantity.toString() else ""
|
||||
holder.time.text = formatTimeAgo(context, item.time)
|
||||
}
|
||||
|
||||
holder.quantity.text = quantityText
|
||||
|
||||
// Listeners
|
||||
if (onItemClickListener != null) {
|
||||
holder.root.setOnClickListener({
|
||||
onItemClickListener?.onItemClick(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int {
|
||||
return items.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago)
|
||||
*/
|
||||
fun formatTimeAgo(context: Context, unixTime: Long): String {
|
||||
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
|
||||
val minutesDiff = secondsDiff / 60
|
||||
|
||||
if (minutesDiff < 1)
|
||||
return context.getString(R.string.now)
|
||||
|
||||
val hoursAgo = (secondsDiff / (60 * 60)).toInt()
|
||||
val minutesAgo = (minutesDiff % 60).toInt()
|
||||
|
||||
if (hoursAgo > 24)
|
||||
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
|
||||
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
|
||||
|
||||
var formattedTime = StringBuilder()
|
||||
if (hoursAgo > 0) {
|
||||
formattedTime.append(hoursAgo).append(" ")
|
||||
if (hoursAgo.toInt() == 1)
|
||||
formattedTime.append(context.getString(R.string.hour_ago))
|
||||
else
|
||||
formattedTime.append(context.getString(R.string.hours_ago))
|
||||
private fun getPreviousWeightEvent(startFromPosition: Int): LunaEvent? {
|
||||
if (startFromPosition == items.size - 1)
|
||||
return null
|
||||
for (pos in startFromPosition + 1 until items.size) {
|
||||
val item = items.get(pos)
|
||||
if (item.type != LunaEvent.TYPE_WEIGHT)
|
||||
continue
|
||||
return item
|
||||
}
|
||||
if (formattedTime.isNotEmpty())
|
||||
formattedTime.append(", ")
|
||||
if (minutesAgo > 0) {
|
||||
formattedTime.append(minutesAgo).append(" ")
|
||||
if (minutesAgo.toInt() == 1)
|
||||
formattedTime.append(context.getString(R.string.minute_ago))
|
||||
else
|
||||
formattedTime.append(context.getString(R.string.minutes_ago))
|
||||
}
|
||||
return formattedTime.toString()
|
||||
return null
|
||||
}
|
||||
|
||||
class LunaEventVH: RecyclerView.ViewHolder {
|
||||
val root: View
|
||||
val type: TextView
|
||||
val description: TextView
|
||||
val quantity: TextView
|
||||
val time: TextView
|
||||
|
||||
constructor(v: View) : super(v) {
|
||||
root = v
|
||||
type = v.findViewById<TextView>(R.id.type)
|
||||
description = v.findViewById<TextView>(R.id.description)
|
||||
quantity = v.findViewById<TextView>(R.id.quantity)
|
||||
time = v.findViewById<TextView>(R.id.time)
|
||||
}
|
||||
}
|
||||
|
||||
interface OnItemClickListener {
|
||||
fun onItemClick(event: LunaEvent)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package it.danieleverducci.lunatracker.entities
|
||||
|
||||
class Logbook {
|
||||
class Logbook(val name: String) {
|
||||
companion object {
|
||||
val MAX_SAFE_LOGBOOK_SIZE = 30000
|
||||
const val MAX_SAFE_LOGBOOK_SIZE = 30000
|
||||
}
|
||||
val logs = ArrayList<LunaEvent>()
|
||||
|
||||
@@ -16,4 +16,8 @@ class Logbook {
|
||||
fun trim() {
|
||||
logs.subList(MAX_SAFE_LOGBOOK_SIZE/2, logs.size).clear()
|
||||
}
|
||||
|
||||
fun sort() {
|
||||
logs.sortDescending()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
package it.danieleverducci.lunatracker.entities
|
||||
|
||||
import android.content.Context
|
||||
import it.danieleverducci.lunatracker.R
|
||||
import org.json.JSONObject
|
||||
import java.util.Date
|
||||
|
||||
@@ -9,16 +11,26 @@ import java.util.Date
|
||||
* allow expandability and backwards compatibility (if a field is added in a
|
||||
* release, it is simply ignored by previous ones).
|
||||
*/
|
||||
class LunaEvent {
|
||||
class LunaEvent: Comparable<LunaEvent> {
|
||||
|
||||
companion object {
|
||||
val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
|
||||
val TYPE_WEIGHT = "WEIGHT"
|
||||
val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
|
||||
val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
|
||||
val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
|
||||
val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
|
||||
val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
|
||||
const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
|
||||
const val TYPE_WEIGHT = "WEIGHT"
|
||||
const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
|
||||
const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
|
||||
const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
|
||||
const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
|
||||
const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
|
||||
const val TYPE_MEDICINE = "MEDICINE"
|
||||
const val TYPE_ENEMA = "ENEMA"
|
||||
const val TYPE_NOTE = "NOTE"
|
||||
const val TYPE_CUSTOM = "CUSTOM"
|
||||
const val TYPE_COLIC = "COLIC"
|
||||
const val TYPE_TEMPERATURE = "TEMPERATURE"
|
||||
const val TYPE_FOOD = "FOOD"
|
||||
const val TYPE_PUKE = "PUKE"
|
||||
const val TYPE_BATH = "BATH"
|
||||
const val TYPE_SLEEP = "SLEEP"
|
||||
}
|
||||
|
||||
private val jo: JSONObject
|
||||
@@ -39,6 +51,17 @@ class LunaEvent {
|
||||
if (value > 0)
|
||||
jo.put("quantity", value)
|
||||
}
|
||||
var notes: String
|
||||
get(): String = jo.optString("notes")
|
||||
set(value) {
|
||||
jo.put("notes", value)
|
||||
}
|
||||
var signature: String
|
||||
get(): String = jo.optString("signature")
|
||||
set(value) {
|
||||
if (value.isNotEmpty())
|
||||
jo.put("signature", value)
|
||||
}
|
||||
|
||||
constructor(jo: JSONObject) {
|
||||
this.jo = jo
|
||||
@@ -60,11 +83,70 @@ class LunaEvent {
|
||||
this.quantity = quantity
|
||||
}
|
||||
|
||||
fun getTypeEmoji(context: Context): String {
|
||||
return context.getString(
|
||||
when (type) {
|
||||
TYPE_BABY_BOTTLE -> R.string.event_bottle_type
|
||||
TYPE_WEIGHT -> R.string.event_scale_type
|
||||
TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
|
||||
TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
|
||||
TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
|
||||
TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
|
||||
TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
|
||||
TYPE_MEDICINE -> R.string.event_medicine_type
|
||||
TYPE_ENEMA -> R.string.event_enema_type
|
||||
TYPE_NOTE -> R.string.event_note_type
|
||||
TYPE_TEMPERATURE -> R.string.event_temperature_type
|
||||
TYPE_COLIC -> R.string.event_colic_type
|
||||
TYPE_FOOD -> R.string.event_food_type
|
||||
TYPE_PUKE -> R.string.event_puke_type
|
||||
TYPE_BATH -> R.string.event_bath_type
|
||||
TYPE_SLEEP -> R.string.event_sleep_type
|
||||
else -> R.string.event_unknown_type
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getTypeDescription(context: Context): String {
|
||||
return context.getString(
|
||||
when (type) {
|
||||
TYPE_BABY_BOTTLE -> R.string.event_bottle_desc
|
||||
TYPE_WEIGHT -> R.string.event_scale_desc
|
||||
TYPE_BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
|
||||
TYPE_BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
|
||||
TYPE_BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
|
||||
TYPE_DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc
|
||||
TYPE_DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc
|
||||
TYPE_MEDICINE -> R.string.event_medicine_desc
|
||||
TYPE_ENEMA -> R.string.event_enema_desc
|
||||
TYPE_NOTE -> R.string.event_note_desc
|
||||
TYPE_TEMPERATURE -> R.string.event_temperature_desc
|
||||
TYPE_COLIC -> R.string.event_colic_desc
|
||||
TYPE_FOOD -> R.string.event_food_desc
|
||||
TYPE_PUKE -> R.string.event_puke_desc
|
||||
TYPE_BATH -> R.string.event_bath_desc
|
||||
TYPE_SLEEP -> R.string.event_sleep_desc
|
||||
else -> R.string.event_unknown_desc
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun getDialogMessage(context: Context): String? {
|
||||
return when(type) {
|
||||
TYPE_MEDICINE -> context.getString(R.string.log_medicine_dialog_description)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
fun toJson(): JSONObject {
|
||||
return jo
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
@@ -9,26 +9,30 @@ import org.json.JSONException
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
import java.io.FilenameFilter
|
||||
|
||||
class FileLogbookRepository: LogbookRepository {
|
||||
companion object {
|
||||
val TAG = "FileLogbookRepository"
|
||||
const val TAG = "FileLogbookRepository"
|
||||
const val FILE_NAME_START = "data"
|
||||
const val FILE_NAME_END = ".json"
|
||||
}
|
||||
|
||||
override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
|
||||
override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
|
||||
try {
|
||||
listener.onLogbookLoaded(loadLogbook(context))
|
||||
listener.onLogbookLoaded(loadLogbook(context, name))
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.d(TAG, "No logbook file found, create one")
|
||||
val newLogbook = Logbook()
|
||||
val newLogbook = Logbook(name)
|
||||
saveLogbook(context, newLogbook)
|
||||
listener.onLogbookLoaded(newLogbook)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadLogbook(context: Context): Logbook {
|
||||
val logbook = Logbook()
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
fun loadLogbook(context: Context, name: String): Logbook {
|
||||
val logbook = Logbook(name)
|
||||
val fileName = getFileName(name)
|
||||
val file = File(context.filesDir, fileName)
|
||||
val json = FileInputStream(file).bufferedReader().use { it.readText() }
|
||||
val ja = JSONArray(json)
|
||||
for (i in 0 until ja.length()) {
|
||||
@@ -53,11 +57,55 @@ class FileLogbookRepository: LogbookRepository {
|
||||
}
|
||||
|
||||
fun saveLogbook(context: Context, logbook: Logbook) {
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
val fileName = getFileName(logbook.name)
|
||||
val file = File(context.filesDir, fileName)
|
||||
val ja = JSONArray()
|
||||
for (l in logbook.logs) {
|
||||
ja.put(l.toJson())
|
||||
}
|
||||
file.writeText(ja.toString())
|
||||
}
|
||||
|
||||
override fun listLogbooks(
|
||||
context: Context,
|
||||
listener: LogbookListObtainedListener
|
||||
) {
|
||||
listener.onLogbookListObtained(listLogbooks(context))
|
||||
}
|
||||
|
||||
fun getAllLogbooks(context: Context): List<Logbook> {
|
||||
val logbooks = arrayListOf<Logbook>()
|
||||
for (logbookName in listLogbooks(context)) {
|
||||
logbooks.add(loadLogbook(context, logbookName))
|
||||
}
|
||||
return logbooks
|
||||
}
|
||||
|
||||
private fun listLogbooks(context: Context): ArrayList<String> {
|
||||
val logbooksFileNames = context.filesDir.list(object: FilenameFilter {
|
||||
override fun accept(dir: File?, name: String?): Boolean {
|
||||
if (name == null)
|
||||
return false
|
||||
if (name.startsWith(FILE_NAME_START) && name.endsWith(FILE_NAME_END))
|
||||
return true
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if (logbooksFileNames == null || logbooksFileNames.isEmpty()) {
|
||||
return arrayListOf()
|
||||
}
|
||||
|
||||
val logbooksNames = arrayListOf<String>()
|
||||
logbooksFileNames.forEach { it ->
|
||||
logbooksNames.add(
|
||||
it.replace("${FILE_NAME_START}_", "").replace(FILE_NAME_START, "").replace(FILE_NAME_END, "")
|
||||
)
|
||||
}
|
||||
return logbooksNames
|
||||
}
|
||||
|
||||
private fun getFileName(name: String): String {
|
||||
return "$FILE_NAME_START${if (name.isNotEmpty()) "_" else ""}${name}$FILE_NAME_END"
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,21 @@ package it.danieleverducci.lunatracker.repository
|
||||
import android.content.Context
|
||||
import android.content.Context.MODE_PRIVATE
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
|
||||
class LocalSettingsRepository(val context: Context) {
|
||||
companion object {
|
||||
val SHARED_PREFS_FILE_NAME = "lunasettings"
|
||||
val SHARED_PREFS_BB_CONTENT = "bbcontent"
|
||||
val SHARED_PREFS_DATA_REPO = "data_repo"
|
||||
val SHARED_PREFS_DAV_URL = "webdav_url"
|
||||
val SHARED_PREFS_DAV_USER = "webdav_user"
|
||||
val SHARED_PREFS_DAV_PASS = "webdav_password"
|
||||
const val SHARED_PREFS_FILE_NAME = "lunasettings"
|
||||
const val SHARED_PREFS_BB_CONTENT = "bbcontent"
|
||||
const val SHARED_PREFS_DATA_REPO = "data_repo"
|
||||
const val SHARED_PREFS_DAV_URL = "webdav_url"
|
||||
const val SHARED_PREFS_DAV_USER = "webdav_user"
|
||||
const val SHARED_PREFS_DAV_PASS = "webdav_password"
|
||||
const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
|
||||
const val SHARED_PREFS_SIGNATURE = "signature"
|
||||
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}
|
||||
val sharedPreferences: SharedPreferences
|
||||
@@ -21,23 +27,39 @@ class LocalSettingsRepository(val context: Context) {
|
||||
}
|
||||
|
||||
fun saveBabyBottleContent(content: Int) {
|
||||
sharedPreferences.edit().putInt(SHARED_PREFS_BB_CONTENT, content).apply()
|
||||
sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
|
||||
}
|
||||
|
||||
fun loadBabyBottleContent(): Int {
|
||||
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
|
||||
}
|
||||
|
||||
fun saveSignature(content: String) {
|
||||
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
|
||||
}
|
||||
|
||||
fun loadSignature(): String {
|
||||
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
|
||||
}
|
||||
|
||||
fun saveNoBreastfeeding(content: Boolean) {
|
||||
sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
|
||||
}
|
||||
|
||||
fun loadNoBreastfeeding(): Boolean {
|
||||
return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false)
|
||||
}
|
||||
|
||||
fun saveDataRepository(repo: DATA_REPO) {
|
||||
val spe = sharedPreferences.edit()
|
||||
spe.putString(
|
||||
SHARED_PREFS_DATA_REPO,
|
||||
when (repo) {
|
||||
DATA_REPO.WEBDAV -> "webdav"
|
||||
DATA_REPO.LOCAL_FILE -> "localfile"
|
||||
}
|
||||
)
|
||||
spe.commit()
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString(
|
||||
SHARED_PREFS_DATA_REPO,
|
||||
when (repo) {
|
||||
DATA_REPO.WEBDAV -> "webdav"
|
||||
DATA_REPO.LOCAL_FILE -> "localfile"
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadDataRepository(): DATA_REPO {
|
||||
@@ -50,11 +72,11 @@ class LocalSettingsRepository(val context: Context) {
|
||||
}
|
||||
|
||||
fun saveWebdavCredentials(url: String, username: String, password: String) {
|
||||
val spe = sharedPreferences.edit()
|
||||
spe.putString(SHARED_PREFS_DAV_URL, url)
|
||||
spe.putString(SHARED_PREFS_DAV_USER, username)
|
||||
spe.putString(SHARED_PREFS_DAV_PASS, password)
|
||||
spe.commit()
|
||||
sharedPreferences.edit(commit = true) {
|
||||
putString(SHARED_PREFS_DAV_URL, url)
|
||||
putString(SHARED_PREFS_DAV_USER, username)
|
||||
putString(SHARED_PREFS_DAV_PASS, password)
|
||||
}
|
||||
}
|
||||
|
||||
fun loadWebdavCredentials(): Array<String>? {
|
||||
@@ -65,4 +87,41 @@ class LocalSettingsRepository(val context: Context) {
|
||||
return null
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,12 @@ import okio.IOException
|
||||
import org.json.JSONException
|
||||
|
||||
interface LogbookRepository {
|
||||
fun loadLogbook(context: Context, listener: LogbookLoadedListener)
|
||||
fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener)
|
||||
companion object {
|
||||
val DEFAULT_LOGBOOK_NAME = "" // For compatibility with older app versions
|
||||
}
|
||||
fun loadLogbook(context: Context, name: String = "", listener: LogbookLoadedListener)
|
||||
fun saveLogbook(context: Context,logbook: Logbook, listener: LogbookSavedListener)
|
||||
fun listLogbooks(context: Context, listener: LogbookListObtainedListener)
|
||||
}
|
||||
|
||||
interface LogbookLoadedListener {
|
||||
@@ -26,3 +30,10 @@ interface LogbookSavedListener {
|
||||
fun onJSONError(error: JSONException)
|
||||
fun onError(error: Exception)
|
||||
}
|
||||
|
||||
interface LogbookListObtainedListener {
|
||||
fun onLogbookListObtained(logbooksNames: ArrayList<String>)
|
||||
fun onIOError(error: IOException)
|
||||
fun onWebDAVError(error: SardineException)
|
||||
fun onError(error: Exception)
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package it.danieleverducci.lunatracker.repository
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import com.thegrizzlylabs.sardineandroid.DavResource
|
||||
import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
|
||||
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
@@ -14,11 +15,13 @@ import java.io.FileNotFoundException
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
import kotlin.io.bufferedReader
|
||||
import kotlin.text.replace
|
||||
|
||||
class WebDAVLogbookRepository(val webDavURL: String, val username: String, val password: String): LogbookRepository {
|
||||
companion object {
|
||||
val TAG = "LogbookRepository"
|
||||
val FILE_NAME = "lunatracker_logbook.json"
|
||||
val FILE_NAME_START = "lunatracker_logbook"
|
||||
val FILE_NAME_END = ".json"
|
||||
}
|
||||
val sardine: OkHttpSardine = OkHttpSardine()
|
||||
|
||||
@@ -29,10 +32,10 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
||||
)
|
||||
}
|
||||
|
||||
override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
|
||||
override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
|
||||
Thread(Runnable {
|
||||
try {
|
||||
val logbook = loadLogbook(context)
|
||||
val logbook = loadLogbook(name)
|
||||
listener.onLogbookLoaded(logbook)
|
||||
} catch (e: SardineException) {
|
||||
Log.e(TAG, e.toString())
|
||||
@@ -52,12 +55,12 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
||||
}).start()
|
||||
}
|
||||
|
||||
private fun loadLogbook(context: Context): Logbook {
|
||||
val inputStream = sardine.get("$webDavURL/$FILE_NAME")
|
||||
private fun loadLogbook(name: String,): Logbook {
|
||||
val inputStream = sardine.get(getUrl(name))
|
||||
val json = inputStream.bufferedReader().use(BufferedReader::readText)
|
||||
inputStream.close()
|
||||
val ja = JSONArray(json)
|
||||
val logbook = Logbook()
|
||||
val logbook = Logbook(name)
|
||||
for (i in 0 until ja.length()) {
|
||||
try {
|
||||
val evt: LunaEvent = LunaEvent(ja.getJSONObject(i))
|
||||
@@ -95,6 +98,40 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
||||
}).start()
|
||||
}
|
||||
|
||||
override fun listLogbooks(
|
||||
context: Context,
|
||||
listener: LogbookListObtainedListener
|
||||
) {
|
||||
Thread(Runnable {
|
||||
try {
|
||||
val logbooksNames = arrayListOf<String>()
|
||||
for (dr: DavResource in sardine.list(webDavURL)){
|
||||
if(!dr.name.startsWith(FILE_NAME_START))
|
||||
continue
|
||||
if(!dr.name.endsWith(FILE_NAME_END))
|
||||
continue
|
||||
logbooksNames.add(
|
||||
dr.name.replace("${FILE_NAME_START}_", "")
|
||||
.replace(FILE_NAME_START, "")
|
||||
.replace(FILE_NAME_END, "")
|
||||
)
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
private fun saveLogbook(context: Context, logbook: Logbook) {
|
||||
// Lock logbook on WebDAV to avoid concurrent changes
|
||||
//sardine.lock(getUrl())
|
||||
@@ -108,64 +145,56 @@ class WebDAVLogbookRepository(val webDavURL: String, val username: String, val p
|
||||
for (l in logbook.logs) {
|
||||
ja.put(l.toJson())
|
||||
}
|
||||
sardine.put(getUrl(), ja.toString().toByteArray())
|
||||
sardine.put(getUrl(logbook.name), ja.toString().toByteArray())
|
||||
}
|
||||
|
||||
/**
|
||||
* Connect to server and check if a logbook already exists.
|
||||
* If it does not exist, try to upload the local one (or create a new one).
|
||||
* If it does not exist, try to upload the local one.
|
||||
* @return error, or null if no error
|
||||
*/
|
||||
fun createLogbook(context: Context, listener: LogbookCreatedListener) {
|
||||
Thread(Runnable {
|
||||
try {
|
||||
loadLogbook(context)
|
||||
listener.onLogbookCreated()
|
||||
} catch (e: SardineException) {
|
||||
if (e.toString().contains("404")) {
|
||||
// Connection successful, but no existing save. Upload the local one.
|
||||
try {
|
||||
val flr = FileLogbookRepository()
|
||||
val logbook = flr.loadLogbook(context)
|
||||
saveLogbook(context, logbook)
|
||||
Log.d(TAG, "Local logbook file found, uploaded")
|
||||
listener.onLogbookCreated()
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.d(TAG, "No local logbook file found, uploading empty file")
|
||||
saveLogbook(context, Logbook())
|
||||
listener.onLogbookCreated()
|
||||
} catch (e: SardineException) {
|
||||
Log.e(TAG, "Unable to upload logbook: $e")
|
||||
listener.onWebDAVError(e)
|
||||
}
|
||||
} else {
|
||||
Log.e(TAG, e.toString())
|
||||
listener.onWebDAVError(e)
|
||||
fun uploadLogbookIfNotExists(context: Context, name: String): String? {
|
||||
val flr = FileLogbookRepository()
|
||||
try {
|
||||
loadLogbook(name)
|
||||
Log.d(TAG, "Logbook file $name already exist on the webDav share: will not overwrite it")
|
||||
return null
|
||||
} catch (e: SardineException) {
|
||||
if (e.toString().contains("404")) {
|
||||
// Connection successful, but logbook does not exist. Upload the local one.
|
||||
try {
|
||||
val logbook = flr.loadLogbook(context, name)
|
||||
saveLogbook(context, logbook)
|
||||
Log.d(TAG, "Local logbook file $name found, uploaded")
|
||||
return null
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.e(TAG, "No local logbook file found, this should not happen!")
|
||||
return "No local logbook file found, app is in inconsistent state, please delete and reinstall it"
|
||||
} catch (e: SardineException) {
|
||||
Log.e(TAG, "Unable to upload logbook: $e")
|
||||
return e.toString()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
} else {
|
||||
Log.e(TAG, e.toString())
|
||||
listener.onIOError(e)
|
||||
} catch (e: SocketTimeoutException) {
|
||||
Log.e(TAG, e.toString())
|
||||
listener.onIOError(e)
|
||||
} catch (e: JSONException) {
|
||||
Log.e(TAG, e.toString())
|
||||
listener.onJSONError(e)
|
||||
} catch (e: Exception) {
|
||||
listener.onError(e)
|
||||
return e.toString()
|
||||
}
|
||||
}).start()
|
||||
} catch (e: IOException) {
|
||||
Log.e(TAG, e.toString())
|
||||
return e.toString()
|
||||
} catch (e: SocketTimeoutException) {
|
||||
Log.e(TAG, e.toString())
|
||||
return e.toString()
|
||||
} catch (e: JSONException) {
|
||||
Log.e(TAG, e.toString())
|
||||
return e.toString()
|
||||
} catch (e: Exception) {
|
||||
return e.toString()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getUrl(): String {
|
||||
return "$webDavURL/$FILE_NAME"
|
||||
}
|
||||
|
||||
|
||||
interface LogbookCreatedListener {
|
||||
fun onLogbookCreated()
|
||||
fun onIOError(error: okio.IOException)
|
||||
fun onWebDAVError(error: SardineException)
|
||||
fun onJSONError(error: JSONException)
|
||||
fun onError(error: Exception)
|
||||
private fun getUrl(name: String): String {
|
||||
val fileName = "${FILE_NAME_START}${if (name.isNotEmpty()) "_" else ""}${name}${FILE_NAME_END}"
|
||||
Log.d(TAG, fileName)
|
||||
return "$webDavURL/$fileName"
|
||||
}
|
||||
}
|
||||
126
app/src/main/java/utils/DateUtils.kt
Normal file
126
app/src/main/java/utils/DateUtils.kt
Normal file
@@ -0,0 +1,126 @@
|
||||
package utils
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.text.format.DateFormat
|
||||
import it.danieleverducci.lunatracker.R
|
||||
import java.util.Date
|
||||
|
||||
class DateUtils {
|
||||
companion object {
|
||||
/**
|
||||
* Format time duration in seconds as e.g. "2 hours, 1 min".
|
||||
* Used for the duration to the next/previous event in the event details dialog.
|
||||
*/
|
||||
fun formatTimeDuration(context: Context, secondsDiff: Long): String {
|
||||
var seconds = secondsDiff
|
||||
|
||||
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".
|
||||
* Used for the event list.
|
||||
*/
|
||||
fun formatTimeAgo(context: Context, unixTime: Long): String {
|
||||
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
|
||||
val minutesDiff = secondsDiff / 60
|
||||
|
||||
if (minutesDiff < 1)
|
||||
return context.getString(R.string.now)
|
||||
|
||||
val hoursAgo = (secondsDiff / (60 * 60)).toInt()
|
||||
val minutesAgo = (minutesDiff % 60).toInt()
|
||||
|
||||
if (hoursAgo > 24)
|
||||
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
|
||||
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
|
||||
|
||||
val formattedTime = StringBuilder()
|
||||
if (hoursAgo > 0) {
|
||||
formattedTime.append(hoursAgo).append(" ")
|
||||
if (hoursAgo == 1)
|
||||
formattedTime.append(context.getString(R.string.hour_ago))
|
||||
else
|
||||
formattedTime.append(context.getString(R.string.hours_ago))
|
||||
}
|
||||
if (minutesAgo > 0) {
|
||||
if (formattedTime.isNotEmpty())
|
||||
formattedTime.append(", ")
|
||||
formattedTime.append(minutesAgo).append(" ")
|
||||
if (minutesAgo == 1)
|
||||
formattedTime.append(context.getString(R.string.minute_ago))
|
||||
else
|
||||
formattedTime.append(context.getString(R.string.minutes_ago))
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
app/src/main/java/utils/NumericUtils.kt
Normal file
117
app/src/main/java/utils/NumericUtils.kt
Normal file
@@ -0,0 +1,117 @@
|
||||
package utils
|
||||
|
||||
import android.content.Context
|
||||
import android.icu.util.LocaleData
|
||||
import android.icu.util.ULocale
|
||||
import android.os.Build
|
||||
import it.danieleverducci.lunatracker.R
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import java.text.NumberFormat
|
||||
|
||||
class NumericUtils (val context: Context) {
|
||||
val numberFormat: NumberFormat
|
||||
val measurement_unit_liquid_base: String
|
||||
val measurement_unit_weight_base: String
|
||||
val measurement_unit_weight_tiny: String
|
||||
val measurement_unit_temperature_base: String
|
||||
|
||||
private fun isMetricSystem(): Boolean {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
|
||||
return (measurementSystem == LocaleData.MeasurementSystem.SI)
|
||||
} else {
|
||||
val locale = context.resources.configuration.locale
|
||||
return when (locale.country) {
|
||||
// https://en.wikipedia.org/wiki/United_States_customary_units
|
||||
// https://en.wikipedia.org/wiki/Imperial_units
|
||||
"US" -> false // US IMPERIAL
|
||||
// UK, Myanmar, Liberia,
|
||||
"GB", "MM", "LR" -> false // IMPERIAL
|
||||
else -> true // METRIC
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
this.numberFormat = NumberFormat.getInstance()
|
||||
this.measurement_unit_liquid_base = context.getString(
|
||||
if (isMetricSystem())
|
||||
R.string.measurement_unit_liquid_base_metric
|
||||
else
|
||||
R.string.measurement_unit_liquid_base_imperial
|
||||
)
|
||||
this.measurement_unit_weight_base = context.getString(
|
||||
if (isMetricSystem())
|
||||
R.string.measurement_unit_weight_base_metric
|
||||
else
|
||||
R.string.measurement_unit_weight_base_imperial
|
||||
)
|
||||
this.measurement_unit_weight_tiny = context.getString(
|
||||
if (isMetricSystem())
|
||||
R.string.measurement_unit_weight_tiny_metric
|
||||
else
|
||||
R.string.measurement_unit_weight_tiny_imperial
|
||||
)
|
||||
this.measurement_unit_temperature_base = context.getString(
|
||||
if (isMetricSystem())
|
||||
R.string.measurement_unit_temperature_base_metric
|
||||
else
|
||||
R.string.measurement_unit_temperature_base_imperial
|
||||
)
|
||||
}
|
||||
|
||||
fun formatEventQuantity(item: LunaEvent): String {
|
||||
val formatted = StringBuilder()
|
||||
if (item.quantity > 0) {
|
||||
formatted.append(when (item.type) {
|
||||
LunaEvent.TYPE_TEMPERATURE ->
|
||||
(item.quantity / 10.0f).toString()
|
||||
LunaEvent.TYPE_PUKE ->
|
||||
context.resources.getStringArray(R.array.AmountLabels)[item.quantity]
|
||||
else ->
|
||||
item.quantity
|
||||
})
|
||||
|
||||
formatted.append(" ")
|
||||
formatted.append(
|
||||
when (item.type) {
|
||||
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
|
||||
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
|
||||
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
|
||||
LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
|
||||
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 -> ""
|
||||
}
|
||||
)
|
||||
}
|
||||
return formatted.toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a valid quantity range for the event type.
|
||||
* @return min, max, normal
|
||||
*/
|
||||
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
|
||||
return when (lunaEventType) {
|
||||
LunaEvent.TYPE_TEMPERATURE -> {
|
||||
if (isMetricSystem())
|
||||
Triple(
|
||||
context.resources.getInteger(R.integer.human_body_temp_min_metric),
|
||||
context.resources.getInteger(R.integer.human_body_temp_max_metric),
|
||||
context.resources.getInteger(R.integer.human_body_temp_normal_metric)
|
||||
)
|
||||
else
|
||||
Triple(
|
||||
context.resources.getInteger(R.integer.human_body_temp_min_imperial),
|
||||
context.resources.getInteger(R.integer.human_body_temp_max_imperial),
|
||||
context.resources.getInteger(R.integer.human_body_temp_normal_imperial)
|
||||
)
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,8 @@
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/accent" />
|
||||
<solid
|
||||
android:color="@color/cardview_dark_background"/>
|
||||
<corners android:radius="15dp" />
|
||||
<padding
|
||||
android:bottom="5dp"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_pressed="true"
|
||||
android:drawable="@drawable/dropdown_list_item_background_pressed"/>
|
||||
<item android:drawable="@drawable/dropdown_list_item_background_released"/>
|
||||
</selector>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/grey" />
|
||||
<solid
|
||||
android:color="@color/grey" />
|
||||
<corners android:radius="15dp" />
|
||||
<padding
|
||||
android:bottom="5dp"
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:top="5dp" />
|
||||
</shape>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/grey" />
|
||||
<solid
|
||||
android:color="@color/cardview_dark_background"/>
|
||||
<corners android:radius="15dp" />
|
||||
<padding
|
||||
android:bottom="5dp"
|
||||
android:left="5dp"
|
||||
android:right="5dp"
|
||||
android:top="5dp" />
|
||||
</shape>
|
||||
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>
|
||||
5
app/src/main/res/drawable/ic_more.xml
Normal file
5
app/src/main/res/drawable/ic_more.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="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
|
||||
</vector>
|
||||
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>
|
||||
12
app/src/main/res/drawable/textview_background.xml
Normal file
12
app/src/main/res/drawable/textview_background.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/grey" />
|
||||
<corners android:radius="10dp" />
|
||||
<padding
|
||||
android:bottom="10dp"
|
||||
android:left="20dp"
|
||||
android:right="20dp"
|
||||
android:top="10dp" />
|
||||
</shape>
|
||||
@@ -6,24 +6,87 @@
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="30dp"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingLeft="15dp"
|
||||
android:paddingRight="15dp">
|
||||
|
||||
<TextView
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/title"
|
||||
android:textSize="30sp"
|
||||
android:gravity="center_horizontal"/>
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
<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
|
||||
android:id="@+id/button_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:layout_gravity="end"
|
||||
android:src="@drawable/ic_settings"
|
||||
app:tint="@color/grey"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/title"
|
||||
android:textSize="26sp"
|
||||
android:gravity="center"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_sync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:layout_gravity="start"
|
||||
android:src="@drawable/ic_sync"
|
||||
app:tint="@color/grey"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/log_an_event"
|
||||
android:gravity="center_horizontal"/>
|
||||
android:layout_height="38dp"
|
||||
android:layout_margin="5dp"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/button_background">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/logbooks_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/logbooks_add_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="10dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textStyle="bold"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="20sp"
|
||||
android:text="+"
|
||||
android:background="@drawable/button_background"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/buttons_container"
|
||||
@@ -41,60 +104,61 @@
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="50dp"
|
||||
android:textSize="50sp"
|
||||
android:text="@string/event_bottle_type"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_scale"
|
||||
android:id="@+id/button_food"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="50dp"
|
||||
android:text="@string/event_scale_type"/>
|
||||
android:textSize="50sp"
|
||||
android:text="@string/event_food_type"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
android:layout_height="wrap_content"
|
||||
android:id="@+id/layout_nipples">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_nipple_left"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:textSize="30sp"
|
||||
android:text="🤱⬅️"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_nipple_both"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:textSize="30sp"
|
||||
android:text="🤱↔️"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_nipple_right"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:textSize="30sp"
|
||||
android:text="🤱➡️️"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -107,43 +171,64 @@
|
||||
android:id="@+id/button_change_poo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="2"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:textSize="30sp"
|
||||
android:text="🚼 💩"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_change_pee"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="2"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30sp"
|
||||
android:text="🚼 💧"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_more"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="5dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/button_background"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:text="🚼 💧"/>
|
||||
android:src="@drawable/ic_more"
|
||||
app:tint="@android:color/darker_gray"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_indicator"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="@color/accent"
|
||||
android:visibility="invisible"/>
|
||||
android:layout_marginTop="5dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Diario di bordo"
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/logbook"
|
||||
android:textColor="@color/accent"
|
||||
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
|
||||
android:id="@+id/list_events"
|
||||
@@ -154,24 +239,6 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_settings"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:layout_gravity="end"
|
||||
android:src="@drawable/ic_settings"
|
||||
app:tint="@color/grey"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/button_sync"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="10dp"
|
||||
android:layout_gravity="start"
|
||||
android:src="@drawable/ic_sync"
|
||||
app:tint="@color/grey"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/no_connection_screen"
|
||||
android:layout_width="match_parent"
|
||||
@@ -194,7 +261,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="30dp"
|
||||
android:textSize="30sp"
|
||||
android:textColor="@color/accent"
|
||||
android:text="@string/no_connection"/>
|
||||
|
||||
@@ -211,7 +278,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:drawableLeft="@drawable/ic_sync"
|
||||
android:drawableStart="@drawable/ic_sync"
|
||||
android:drawableTint="@color/white"
|
||||
android:drawablePadding="10dp"
|
||||
android:text="@string/no_connection_retry"
|
||||
@@ -222,7 +289,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:drawableLeft="@drawable/ic_settings"
|
||||
android:drawableStart="@drawable/ic_settings"
|
||||
android:drawableTint="@color/white"
|
||||
android:drawablePadding="10dp"
|
||||
android:text="@string/no_connection_go_to_settings"
|
||||
|
||||
@@ -39,26 +39,26 @@
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:text="@string/settings_storage_local_desc"/>
|
||||
|
||||
<RadioButton android:id="@+id/settings_data_webdav"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="30dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:text="@string/settings_storage_dav"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:text="@string/settings_storage_dav_desc"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_storage_dav_url"/>
|
||||
|
||||
@@ -66,15 +66,16 @@
|
||||
android:id="@+id/settings_data_webdav_url"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:hint="@string/settings_storage_dav_url_hint"
|
||||
android:inputType="textUri"/>
|
||||
android:inputType="textUri"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_storage_dav_user"/>
|
||||
|
||||
@@ -82,23 +83,31 @@
|
||||
android:id="@+id/settings_data_webdav_user"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:inputType="textEmailAddress"/>
|
||||
android:layout_marginStart="30dp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:layout_marginStart="30dp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_storage_dav_pass"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/settings_data_webdav_pass"
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="30dp"
|
||||
android:inputType="textPassword"/>
|
||||
android:layout_marginStart="30dp"
|
||||
app:passwordToggleEnabled="true">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/settings_data_webdav_pass"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/textview_background"
|
||||
android:inputType="textPassword" />
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_indicator"
|
||||
@@ -106,32 +115,126 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="@color/accent"
|
||||
android:layout_margin="20dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_cancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@android:string/cancel"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_save"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/accent"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</LinearLayout>
|
||||
</RadioGroup>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/settings_signature" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/settings_signature"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="20dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:inputType="textEmailAddress"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp"
|
||||
android:layout_marginTop="5dp"
|
||||
android:text="@string/settings_signature_desc"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:layout_marginTop="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">
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_cancel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginEnd="20dp"
|
||||
android:background="@drawable/button_background"
|
||||
android:textColor="@color/accent"
|
||||
android:text="@android:string/cancel"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/settings_save"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:textColor="@color/accent"
|
||||
android:background="@drawable/button_background"
|
||||
android:text="@android:string/ok"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
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>
|
||||
25
app/src/main/res/layout/dialog_add_logbook.xml
Normal file
25
app/src/main/res/layout/dialog_add_logbook.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_add_logbook_message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/dialog_add_logbook_message"/>
|
||||
|
||||
|
||||
<EditText
|
||||
android:id="@+id/dialog_add_logbook_logbookname"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:lines="1"
|
||||
android:inputType="text"
|
||||
android:hint="@string/dialog_add_logbook_logbookname"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
</LinearLayout>
|
||||
102
app/src/main/res/layout/dialog_event_detail.xml
Normal file
102
app/src/main/res/layout/dialog_event_detail.xml
Normal file
@@ -0,0 +1,102 @@
|
||||
<?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:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:paddingHorizontal="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_event_detail_type_emoji"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="60sp"
|
||||
android:text="@string/event_diaperchange_pee_type" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_event_detail_type_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="@color/accent"
|
||||
android:textSize="32sp"
|
||||
android:text="@string/event_diaperchange_pee_desc" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_event_detail_type_date"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:drawablePadding="10dp"
|
||||
android:drawableTint="@color/accent"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:text="@string/dialog_event_detail_datetime_icon"
|
||||
app:drawableEndCompat="@drawable/ic_edit" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_event_detail_type_quantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:textSize="28sp"
|
||||
android:text="@string/dialog_event_detail_quantity"/>
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:layout_marginTop="20dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_event_detail_type_notes"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="italic"
|
||||
android:textSize="20sp"
|
||||
android:text="@string/dialog_event_detail_notes"/>
|
||||
|
||||
</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>
|
||||
27
app/src/main/res/layout/dialog_notes.xml
Normal file
27
app/src/main/res/layout/dialog_notes.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="vertical"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="20dp">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/notes_qty_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:lines="1"
|
||||
android:inputType="number"
|
||||
android:hint="@string/log_notes_dialog_qty_hint"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
<EditText
|
||||
android:id="@+id/notes_edittext"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp"
|
||||
android:gravity="top"
|
||||
android:lines="10"
|
||||
android:hint="@string/log_notes_dialog_note_hint"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
</LinearLayout>
|
||||
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>
|
||||
104
app/src/main/res/layout/more_events_popup.xml
Normal file
104
app/src/main/res/layout/more_events_popup.xml
Normal file
@@ -0,0 +1,104 @@
|
||||
<?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="10dp"
|
||||
android:background="@color/transparent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
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
|
||||
android:id="@+id/button_medicine"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_medicine"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_note"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_note"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_temperature"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_temperature"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_puke"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_puke"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_colic"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_colic"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_scale"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_scale"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_bath"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_bath"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/button_enema"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginTop="10dp"
|
||||
android:padding="10dp"
|
||||
android:background="@drawable/dropdown_list_item_background"
|
||||
style="@style/OverflowMenuText"
|
||||
android:text="@string/overflow_event_enema"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</ScrollView>
|
||||
@@ -11,11 +11,12 @@
|
||||
android:layout_width="150dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="number"
|
||||
android:hint="0"/>
|
||||
android:hint="0"
|
||||
android:background="@drawable/textview_background"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="g"/>
|
||||
</LinearLayout>
|
||||
|
||||
@@ -14,6 +14,6 @@
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="10dp"
|
||||
android:layout_marginStart="10dp"
|
||||
android:text="ml"/>
|
||||
</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
app/src/main/res/layout/row_logbook_spinner.xml
Normal file
11
app/src/main/res/layout/row_logbook_spinner.xml
Normal file
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@android:id/text1"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingEnd="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:singleLine="true"
|
||||
android:ellipsize="end"/>
|
||||
@@ -3,30 +3,39 @@
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/type"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:paddingStart="10dp"
|
||||
android:textSize="28sp"
|
||||
android:text="Type"/>
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:text="@string/event_diaperchange_pee_type"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:text="Description"/>
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textColor="@color/accent"
|
||||
android:text="@string/row_luna_event_description"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quantity"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="Qty"/>
|
||||
android:text="@string/row_luna_event_quantity"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
@@ -35,6 +44,8 @@
|
||||
android:layout_weight="2"
|
||||
android:gravity="center_horizontal"
|
||||
android:textStyle="bold"
|
||||
android:text="Time"/>
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:text="@string/row_luna_event_time"/>
|
||||
|
||||
</LinearLayout>
|
||||
59
app/src/main/res/layout/row_luna_event_vertical.xml
Normal file
59
app/src/main/res/layout/row_luna_event_vertical.xml
Normal file
@@ -0,0 +1,59 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:orientation="horizontal"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingTop="10dp"
|
||||
android:paddingBottom="10dp"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/type"
|
||||
android:layout_width="90dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="28sp"
|
||||
android:lines="1"
|
||||
android:maxLines="1"
|
||||
android:text="@string/event_diaperchange_pee_type"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:gravity="center_horizontal"
|
||||
android:textColor="@color/accent"
|
||||
android:text="@string/row_luna_event_description"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quantity"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:layout_marginLeft="5dp"
|
||||
android:layout_marginRight="5dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:text="@string/dialog_event_detail_quantity"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/time"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="2"
|
||||
android:gravity="center_horizontal"
|
||||
android:textStyle="bold"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:text="@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>
|
||||
26
app/src/main/res/layout/temperature_dialog.xml
Normal file
26
app/src/main/res/layout/temperature_dialog.xml
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/dialog_temperature_value"
|
||||
android:layout_width="250dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:valueFrom="@integer/human_body_temp_min_metric"
|
||||
android:valueTo="@integer/human_body_temp_max_metric"
|
||||
android:stepSize="0.1"
|
||||
android:value="@integer/human_body_temp_normal_metric"
|
||||
android:theme="@style/LTSlider"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/dialog_temperature_display"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="30sp"
|
||||
android:textColor="@color/accent"/>
|
||||
</LinearLayout>
|
||||
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>
|
||||
<string name="app_name">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="log_bottle_dialog_title">Biberon</string>
|
||||
@@ -10,22 +9,29 @@
|
||||
<string name="log_weight_dialog_title">Pesata</string>
|
||||
<string name="log_weight_dialog_description">Inserisci il peso rilevato</string>
|
||||
|
||||
<string name="event_bottle_type">🍼</string>
|
||||
<string name="event_scale_type">⚖️</string>
|
||||
<string name="event_breastfeeding_left_type">🤱 ←</string>
|
||||
<string name="event_breastfeeding_both_type">🤱 ↔</string>
|
||||
<string name="event_breastfeeding_right_type">🤱 →</string>
|
||||
<string name="event_diaperchange_poo_type">🚼 💩</string>
|
||||
<string name="event_diaperchange_pee_type">🚼 💧</string>
|
||||
<string name="event_unknown_type">-</string>
|
||||
<string name="log_temperature_dialog_title">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_enema">🪠 Clistere</string>
|
||||
<string name="overflow_event_note">📝 Nota</string>
|
||||
<string name="overflow_event_temperature">🌡️ Temperatura</string>
|
||||
<string name="overflow_event_colic">💨 Colichette</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_breastfeeding_left_desc">Allatt. al seno (sx)</string>
|
||||
<string name="event_breastfeeding_both_desc">Allatt. al seno</string>
|
||||
<string name="event_breastfeeding_right_desc">Allatt. al seno (dx)</string>
|
||||
<string name="event_diaperchange_poo_desc">Cambio (con cacca)</string>
|
||||
<string name="event_diaperchange_pee_desc">Cambio (con pipì)</string>
|
||||
<string name="event_medicine_desc">Medicina</string>
|
||||
<string name="event_enema_desc">Clistere</string>
|
||||
<string name="event_note_desc">Nota</string>
|
||||
<string name="event_temperature_desc">Temperatura</string>
|
||||
<string name="event_colic_desc">Colichette</string>
|
||||
<string name="event_unknown_desc"></string>
|
||||
|
||||
<string name="toast_event_added">Evento aggiunto</string>
|
||||
@@ -49,18 +55,20 @@
|
||||
<string name="settings_storage_local">Sul dispositivo</string>
|
||||
<string name="settings_storage_local_desc">La soluzione più privacy-friendly: i dati non escono mai dal tuo dispositivo</string>
|
||||
<string name="settings_storage_dav">Su un server WebDAV</string>
|
||||
<string name="settings_storage_dav_desc">Puoi usare un qualunque servizio che supporti WebDAV (come ad esempio Nextcloud, Owncloud, Dropbox, Box...) per salvare i dati. In questo modo puoi sincronizzarli tra più dispositivi, ad esempio quello del papà, della mamma, dei nonni... Ti servirà l\'url WebDAV, che trovi nella documentazione del tuo provider (ad es. in Nextcloud è nelle impostazioni di Files)</string>
|
||||
<string name="settings_storage_dav_desc">Puoi usare un qualunque servizio che supporti WebDAV (come ad esempio Nextcloud, Owncloud, Dropbox, Box…) per salvare i dati. In questo modo puoi sincronizzarli tra più dispositivi, ad esempio quello del papà, della mamma, dei nonni… Ti servirà l\'url WebDAV, che trovi nella documentazione del tuo provider (ad es. in Nextcloud è nelle impostazioni di Files)</string>
|
||||
<string name="settings_storage_dav_url">Url WebDAV</string>
|
||||
<string name="settings_storage_dav_url_hint">https://</string>
|
||||
<string name="settings_storage_dav_user">Username</string>
|
||||
<string name="settings_storage_dav_pass">Password</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_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_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_json_error">Sul server esiste un salvataggio, ma è corrotto o illeggibile. Cancellare il file </string>
|
||||
<string name="settings_generic_error">Si è verificato un errore: </string>
|
||||
<string name="settings_webdav_upload_error">Errore durante l\'upload del logbook locale %1$s su webdav: %2$s</string>
|
||||
|
||||
<string name="trim_logbook_dialog_title">Il tuo diario è bello grande!</string>
|
||||
<string name="trim_logbook_dialog_message_local">Il file del tuo diario sta crescendo molto. Ti suggeriamo di cancellare gli eventi più vecchi per evitare problemi di memoria.</string>
|
||||
@@ -68,4 +76,110 @@
|
||||
<string name="trim_logbook_dialog_button_ok">Cancella i più vecchi</string>
|
||||
<string name="trim_logbook_dialog_button_cancel">Ricordamelo dopo</string>
|
||||
|
||||
<string name="log_notes_dialog_description">Note:</string>
|
||||
<string name="log_medicine_dialog_description">Nome della medicina, quantità, formato, note…:</string>
|
||||
<string name="log_notes_dialog_qty_hint">Quantità, o vuoto</string>
|
||||
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
|
||||
|
||||
<string name="dialog_event_detail_title">Dettaglio evento</string>
|
||||
<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_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_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="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>
|
||||
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>
|
||||
@@ -4,5 +4,10 @@
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
<color name="translucent">#c000</color>
|
||||
<color name="transparent">#0000</color>
|
||||
<color name="grey">#ccc</color>
|
||||
<color name="list_background_odd">#423B25</color>
|
||||
<color name="list_background_even">@color/transparent</color>
|
||||
<color name="danger">#f00</color>
|
||||
<color name="textColor">@color/grey</color>
|
||||
</resources>
|
||||
@@ -1,7 +1,6 @@
|
||||
<resources>
|
||||
<string name="app_name">LunaTracker</string>
|
||||
<string name="title">🌜 LunaTracker 🌛</string>
|
||||
<string name="log_an_event">Log an event:</string>
|
||||
<string name="logbook">Logged events</string>
|
||||
|
||||
<string name="log_bottle_dialog_title">Baby bottle</string>
|
||||
@@ -10,34 +9,77 @@
|
||||
<string name="log_weight_dialog_title">Weight</string>
|
||||
<string name="log_weight_dialog_description">Insert the weight</string>
|
||||
|
||||
<string name="event_bottle_type">🍼</string>
|
||||
<string name="event_scale_type">⚖️</string>
|
||||
<string name="event_breastfeeding_left_type">🤱 ←</string>
|
||||
<string name="event_breastfeeding_both_type">🤱 ↔</string>
|
||||
<string name="event_breastfeeding_right_type">🤱 →</string>
|
||||
<string name="event_diaperchange_poo_type">🚼 💩</string>
|
||||
<string name="event_diaperchange_pee_type">🚼 💧</string>
|
||||
<string name="event_unknown_type">-</string>
|
||||
<string name="log_temperature_dialog_title">Temperature</string>
|
||||
<string name="log_temperature_dialog_description">Insert the temperature</string>
|
||||
|
||||
<string name="log_puke_dialog_title">Puke</string>
|
||||
<string name="log_puke_dialog_description">Select the amount</string>
|
||||
|
||||
<string name="event_bottle_type" translatable="false">🍼</string>
|
||||
<string name="event_food_type" translatable="false">🥣</string>
|
||||
<string name="event_scale_type" translatable="false">⚖️</string>
|
||||
<string name="event_breastfeeding_left_type" translatable="false">🤱 ←</string>
|
||||
<string name="event_breastfeeding_both_type" translatable="false">🤱 ↔</string>
|
||||
<string name="event_breastfeeding_right_type" translatable="false">🤱 →</string>
|
||||
<string name="event_diaperchange_poo_type" translatable="false">🚼 💩</string>
|
||||
<string name="event_diaperchange_pee_type" translatable="false">🚼 💧</string>
|
||||
<string name="event_medicine_type" translatable="false">💊</string>
|
||||
<string name="event_enema_type" translatable="false">🪠</string>
|
||||
<string name="event_note_type" translatable="false">📝</string>
|
||||
<string name="event_temperature_type" translatable="false">🌡️</string>
|
||||
<string name="event_colic_type" translatable="false">💨</string>
|
||||
<string name="event_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_bottle_desc">Baby bottle</string>
|
||||
<string name="event_food_desc">Food</string>
|
||||
<string name="event_scale_desc">Weight</string>
|
||||
<string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
|
||||
<string name="event_breastfeeding_both_desc">Breastfeeding</string>
|
||||
<string name="event_breastfeeding_right_desc">Breastfeeding (right)</string>
|
||||
<string name="event_diaperchange_poo_desc">Diaper chg (poo)</string>
|
||||
<string name="event_diaperchange_pee_desc">Diaper chg (pee)</string>
|
||||
<string name="event_medicine_desc">Medicine</string>
|
||||
<string name="event_enema_desc">Enema</string>
|
||||
<string name="event_note_desc">Note</string>
|
||||
<string name="event_temperature_desc">Temperature</string>
|
||||
<string name="event_colic_desc">Gaseous colic</string>
|
||||
<string name="event_puke_desc">Puke</string>
|
||||
<string name="event_bath_desc">Bath</string>
|
||||
<string name="event_sleep_desc">Sleep</string>
|
||||
<string name="event_unknown_desc"></string>
|
||||
|
||||
<string name="overflow_event_scale">⚖️ Weight</string>
|
||||
<string name="overflow_event_medicine">💊 Medicine</string>
|
||||
<string name="overflow_event_enema">🪠 Enema</string>
|
||||
<string name="overflow_event_note">📝 Note</string>
|
||||
<string name="overflow_event_temperature">🌡️ Temperature</string>
|
||||
<string name="overflow_event_colic">💨 Gaseous colic</string>
|
||||
<string name="overflow_event_puke">🤮 Puke</string>
|
||||
<string name="overflow_event_bath">🛁 Bath</string>
|
||||
|
||||
<string name="toast_event_added">Event logged</string>
|
||||
<string name="toast_logbook_saved">Logbook saved</string>
|
||||
<string name="toast_event_add_error">Unable to log the event</string>
|
||||
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
|
||||
|
||||
<string name="now">now</string>
|
||||
<string name="hour_ago">hour</string>
|
||||
<string name="hours_ago">hours</string>
|
||||
<string name="second_ago">sec</string>
|
||||
<string name="seconds_ago">secs</string>
|
||||
<string name="minute_ago">min</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_explain">Unable to reach WebDAV service</string>
|
||||
@@ -45,27 +87,156 @@
|
||||
<string name="no_connection_retry">Retry</string>
|
||||
|
||||
<string name="settings_title">Settings</string>
|
||||
<string name="settings_signature">Signature</string>
|
||||
<string name="settings_signature_desc">Attach a signature to each event you create and for others to see. Useful if multiple people add events.</string>
|
||||
<string name="settings_storage">Choose where to save data</string>
|
||||
<string name="settings_storage_local">On device</string>
|
||||
<string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string>
|
||||
<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_url">WebDAV url</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_hint">https://</string>
|
||||
<string name="settings_storage_dav_user">Username</string>
|
||||
<string name="settings_storage_dav_pass">Password</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_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_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_json_error">There\'s a save file on the server, but is corrupted or unreadable. Please delete it </string>
|
||||
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
|
||||
<string name="settings_no_breastfeeding">No Breastfeeding</string>
|
||||
<string name="settings_no_breastfeeding_desc">Hide the Breastfeeding buttons for when they are not needed.</string>
|
||||
<string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string>
|
||||
<string name="settings_generic_error">Error: </string>
|
||||
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
|
||||
|
||||
<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_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_cancel">Remind me later</string>
|
||||
|
||||
<string name="log_notes_dialog_description">Notes:</string>
|
||||
<string name="log_medicine_dialog_description">Medicine name, quantity, type, notes…:</string>
|
||||
<string name="log_notes_dialog_qty_hint">Quantity (or empty)</string>
|
||||
<string name="log_notes_dialog_note_hint">Write some notes</string>
|
||||
|
||||
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</string>
|
||||
<string name="measurement_unit_weight_base_metric" translatable="false">g</string>
|
||||
<string name="measurement_unit_weight_tiny_metric" translatable="false">mg</string>
|
||||
<string name="measurement_unit_liquid_base_imperial" translatable="false">fl oz.</string>
|
||||
<string name="measurement_unit_weight_base_imperial" translatable="false">oz</string>
|
||||
<string name="measurement_unit_weight_tiny_imperial" translatable="false">gr</string>
|
||||
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
|
||||
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
|
||||
|
||||
<string name="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_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_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_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="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>
|
||||
@@ -1,5 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar" />
|
||||
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
|
||||
<item name="colorAccent">@color/accent</item>
|
||||
<item name="android:textColor">@color/textColor</item>
|
||||
</style>
|
||||
|
||||
<style name="OverflowMenuText">
|
||||
<item name="android:textSize">20sp</item>
|
||||
</style>
|
||||
|
||||
<style name="LTSlider" parent="Theme.MaterialComponents.Light.NoActionBar">
|
||||
<item name="colorPrimary">@color/accent</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
9
app/src/main/res/values/values.xml
Normal file
9
app/src/main/res/values/values.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<integer name="human_body_temp_min_imperial">91</integer>
|
||||
<integer name="human_body_temp_min_metric">33</integer>
|
||||
<integer name="human_body_temp_max_imperial">109</integer>
|
||||
<integer name="human_body_temp_max_metric">43</integer>
|
||||
<integer name="human_body_temp_normal_imperial">98</integer>
|
||||
<integer name="human_body_temp_normal_metric">37</integer>
|
||||
</resources>
|
||||
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,13 +1,12 @@
|
||||
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.
|
||||
You have to react fast, event if it's 4 AM and you have no idea why is crying.
|
||||
You have to react fast, even if it's 4 AM and you have no idea 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.
|
||||
|
||||
|
||||
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
|
||||
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 🌛
|
||||
@@ -1,5 +1,3 @@
|
||||
LunaTracker è un'app di tracciamento neonatale
|
||||
|
||||
Essere genitori può essere dura. Arrivate a casa dall'ospedale, esausti, con questo cosino fragile e sconosciuto che non ha un manuale d'uso, e ha un solo modo per segnalare che c'è qualcosa che non va: piangere.
|
||||
Dovete essere sempre pronti a reagire, anche se sono le 4 di mattina e non avete idea del perché stia piangendo.
|
||||
|
||||
@@ -8,6 +6,7 @@ Quest'app è pensata per memorizzare tutti gli eventi del bambino (cambio di pan
|
||||
Dedicato a mia figlia Luna.
|
||||
|
||||
|
||||
Un grandissimo ringraziamento a tutti i contributors! Vedi https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
|
||||
|
||||
NOTA: il contenuto di quest'app ha solo scopo informativo o didattico e non sostituisce il consulto medico professionale o le consulenze con operatori sanitari.
|
||||
Feature graphic ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[versions]
|
||||
agp = "8.7.2"
|
||||
agp = "8.13.0"
|
||||
kotlin = "2.0.0"
|
||||
coreKtx = "1.10.1"
|
||||
junit = "4.13.2"
|
||||
|
||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@@ -1,6 +1,6 @@
|
||||
#Sat Nov 02 10:58:51 CET 2024
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
||||
Reference in New Issue
Block a user