Compare commits
21 Commits
master
...
6f69b581e4
| Author | SHA1 | Date | |
|---|---|---|---|
| 6f69b581e4 | |||
| ec80f78baf | |||
| 2f877bdec7 | |||
| 22e5e3ddec | |||
| 2ae5a2deda | |||
| e8480176c3 | |||
| 1c90fbd7c4 | |||
| b164a80531 | |||
| ae965ebe5e | |||
| daac0d063f | |||
| eab55de651 | |||
| 7cf396026b | |||
| 6759956461 | |||
| c521a3373f | |||
| 1ad5f31b32 | |||
| 9f8f277d75 | |||
| 742c5515b1 | |||
| d49701a488 | |||
| fb6edf981b | |||
| 52b23151d6 | |||
| 23372408d5 |
19
README.md
19
README.md
@@ -13,24 +13,7 @@ Dedicated to my daughter Luna.
|
|||||||
|
|
||||||

|

|
||||||
|
|
||||||
## Contributions
|
## Thanks for the valuable contributions to:
|
||||||
|
|
||||||
### Why isn't this hosted on GitHub?
|
|
||||||
|
|
||||||
I'm using a private git server just because I'm worried for the vast majority of open source code being hosted in a server property of Microsoft and being used to train theirs AI.
|
|
||||||
I didn't find a better option, BTW the Gitea project is working on implementing federation, so soon it will be possible to contribute using any other gitea server, selfhosted or not.
|
|
||||||
|
|
||||||
### How to contribute
|
|
||||||
|
|
||||||
The project is open to contribution, but with some limits:
|
|
||||||
|
|
||||||
- I'm sorry I can't accept AI-generated contributions. Reviewing a contribution requires time and effort from my side, while generating code with AI requires very little time and produces non reliable code that must be reviewed in detail. This is effectively shifting the work on my side, and in a forced way. If you feel you need a feature but you're not able to implement it by yourself, I prefer you to create an issue in the repository so I can implement it when I can, in a more mantainable way.
|
|
||||||
- I prefer to make project-wide changes (i.e. updating Android target, app name and icon, release number...) by myself.
|
|
||||||
|
|
||||||
To contribute, you'll have to create an account on this git instance. Unfortunately, I had to disable registration to avoid huge waves of fake accounts created by bots.
|
|
||||||
You can request an account writing to daniele.verducci@ichibi.eu
|
|
||||||
|
|
||||||
### Thanks for the valuable contributions to:
|
|
||||||
|
|
||||||
- Chepycou (French translation)
|
- Chepycou (French translation)
|
||||||
- Daniel Neubauer (German translation)
|
- Daniel Neubauer (German translation)
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "it.danieleverducci.lunatracker"
|
namespace = "it.danieleverducci.lunatracker"
|
||||||
compileSdk = 34
|
compileSdk = 36
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId = "it.danieleverducci.lunatracker"
|
applicationId = "it.danieleverducci.lunatracker"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 34
|
targetSdk = 36
|
||||||
versionCode = 7
|
versionCode = 7
|
||||||
versionName = "0.9"
|
versionName = "0.9"
|
||||||
|
|
||||||
@@ -31,6 +31,12 @@ android {
|
|||||||
sourceCompatibility = JavaVersion.VERSION_11
|
sourceCompatibility = JavaVersion.VERSION_11
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
targetCompatibility = JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
|
dependenciesInfo {
|
||||||
|
// Disables dependency metadata when building APKs.
|
||||||
|
includeInApk = false
|
||||||
|
// Disables dependency metadata when building Android App Bundles.
|
||||||
|
includeInBundle = false
|
||||||
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "11"
|
jvmTarget = "11"
|
||||||
}
|
}
|
||||||
@@ -40,7 +46,6 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(libs.androidx.core.ktx)
|
implementation(libs.androidx.core.ktx)
|
||||||
implementation(libs.androidx.lifecycle.runtime.ktx)
|
implementation(libs.androidx.lifecycle.runtime.ktx)
|
||||||
implementation(libs.androidx.activity.compose)
|
implementation(libs.androidx.activity.compose)
|
||||||
@@ -51,7 +56,7 @@ dependencies {
|
|||||||
implementation(libs.androidx.material3)
|
implementation(libs.androidx.material3)
|
||||||
implementation(libs.androidx.appcompat)
|
implementation(libs.androidx.appcompat)
|
||||||
implementation(libs.androidx.recyclerview)
|
implementation(libs.androidx.recyclerview)
|
||||||
implementation("com.github.thegrizzlylabs:sardine-android:v0.9")
|
implementation(libs.sardine.android)
|
||||||
implementation(libs.material)
|
implementation(libs.material)
|
||||||
testImplementation(libs.junit)
|
testImplementation(libs.junit)
|
||||||
androidTestImplementation(libs.androidx.junit)
|
androidTestImplementation(libs.androidx.junit)
|
||||||
@@ -60,4 +65,5 @@ dependencies {
|
|||||||
androidTestImplementation(libs.androidx.ui.test.junit4)
|
androidTestImplementation(libs.androidx.ui.test.junit4)
|
||||||
debugImplementation(libs.androidx.ui.tooling)
|
debugImplementation(libs.androidx.ui.tooling)
|
||||||
debugImplementation(libs.androidx.ui.test.manifest)
|
debugImplementation(libs.androidx.ui.test.manifest)
|
||||||
|
implementation(libs.mpandroidchart.vv310)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,10 @@
|
|||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="@string/settings_title"
|
android:label="@string/settings_title"
|
||||||
android:theme="@style/Theme.LunaTracker"/>
|
android:theme="@style/Theme.LunaTracker"/>
|
||||||
|
<activity
|
||||||
|
android:name=".StatisticsActivity"
|
||||||
|
android:label="@string/statistics_title"
|
||||||
|
android:theme="@style/Theme.LunaTracker"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@ import android.os.Bundle
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.RadioButton
|
import android.widget.RadioButton
|
||||||
|
import android.widget.Spinner
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@@ -24,7 +25,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
protected lateinit var textViewWebDAVUser: TextView
|
protected lateinit var textViewWebDAVUser: TextView
|
||||||
protected lateinit var textViewWebDAVPass: TextView
|
protected lateinit var textViewWebDAVPass: TextView
|
||||||
protected lateinit var progressIndicator: LinearProgressIndicator
|
protected lateinit var progressIndicator: LinearProgressIndicator
|
||||||
protected lateinit var switchNoBreastfeeding: SwitchMaterial
|
protected lateinit var switchDynamicMenu: SwitchMaterial
|
||||||
protected lateinit var textViewSignature: EditText
|
protected lateinit var textViewSignature: EditText
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@@ -37,7 +38,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
|
textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user)
|
||||||
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
|
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
|
||||||
progressIndicator = findViewById(R.id.progress_indicator)
|
progressIndicator = findViewById(R.id.progress_indicator)
|
||||||
switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
|
switchDynamicMenu = findViewById(R.id.switch_dynamic_menu)
|
||||||
textViewSignature = findViewById(R.id.settings_signature)
|
textViewSignature = findViewById(R.id.settings_signature)
|
||||||
|
|
||||||
findViewById<View>(R.id.settings_save).setOnClickListener({
|
findViewById<View>(R.id.settings_save).setOnClickListener({
|
||||||
@@ -54,7 +55,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
fun loadSettings() {
|
fun loadSettings() {
|
||||||
val dataRepo = settingsRepository.loadDataRepository()
|
val dataRepo = settingsRepository.loadDataRepository()
|
||||||
val webDavCredentials = settingsRepository.loadWebdavCredentials()
|
val webDavCredentials = settingsRepository.loadWebdavCredentials()
|
||||||
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
|
val dynamicMenu = settingsRepository.loadDynamicMenu()
|
||||||
val signature = settingsRepository.loadSignature()
|
val signature = settingsRepository.loadSignature()
|
||||||
|
|
||||||
when (dataRepo) {
|
when (dataRepo) {
|
||||||
@@ -63,7 +64,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textViewSignature.setText(signature)
|
textViewSignature.setText(signature)
|
||||||
switchNoBreastfeeding.isChecked = noBreastfeeding
|
switchDynamicMenu.isChecked = dynamicMenu
|
||||||
|
|
||||||
if (webDavCredentials != null) {
|
if (webDavCredentials != null) {
|
||||||
textViewWebDAVUrl.text = webDavCredentials[0]
|
textViewWebDAVUrl.text = webDavCredentials[0]
|
||||||
@@ -160,7 +161,7 @@ open class SettingsActivity : AppCompatActivity() {
|
|||||||
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
|
if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV
|
||||||
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
|
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
|
||||||
)
|
)
|
||||||
settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
|
settingsRepository.saveDynamicMenu(switchDynamicMenu.isChecked)
|
||||||
settingsRepository.saveSignature(textViewSignature.text.toString())
|
settingsRepository.saveSignature(textViewSignature.text.toString())
|
||||||
settingsRepository.saveWebdavCredentials(
|
settingsRepository.saveWebdavCredentials(
|
||||||
textViewWebDAVUrl.text.toString(),
|
textViewWebDAVUrl.text.toString(),
|
||||||
|
|||||||
@@ -0,0 +1,976 @@
|
|||||||
|
package it.danieleverducci.lunatracker
|
||||||
|
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.AdapterView
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import android.widget.Spinner
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.graphics.toColorInt
|
||||||
|
import com.github.mikephil.charting.charts.BarChart
|
||||||
|
import com.github.mikephil.charting.data.BarData
|
||||||
|
import com.github.mikephil.charting.data.BarDataSet
|
||||||
|
import com.github.mikephil.charting.data.BarEntry
|
||||||
|
import com.github.mikephil.charting.data.Entry
|
||||||
|
import com.github.mikephil.charting.formatter.ValueFormatter
|
||||||
|
import com.github.mikephil.charting.highlight.Highlight
|
||||||
|
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet
|
||||||
|
import com.github.mikephil.charting.listener.OnChartValueSelectedListener
|
||||||
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
|
import utils.DateUtils
|
||||||
|
import utils.NumericUtils
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
|
||||||
|
class StatisticsActivity : AppCompatActivity() {
|
||||||
|
var lastToastShown = 0L
|
||||||
|
|
||||||
|
lateinit var barChart: BarChart
|
||||||
|
lateinit var noDataTextView: TextView
|
||||||
|
lateinit var graphTypeSpinner: Spinner
|
||||||
|
lateinit var timeRangeSpinner: Spinner
|
||||||
|
|
||||||
|
lateinit var unixToSpan: (Long) -> Int
|
||||||
|
lateinit var spanToUnix: (Int) -> Long
|
||||||
|
|
||||||
|
enum class GraphType {
|
||||||
|
BOTTLE_EVENTS,
|
||||||
|
BOTTLE_SUM,
|
||||||
|
SLEEP_SUM,
|
||||||
|
SLEEP_EVENTS,
|
||||||
|
SLEEP_PATTERN,
|
||||||
|
MEDICINE_EVENTS
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TimeRange {
|
||||||
|
DAY,
|
||||||
|
WEEK,
|
||||||
|
MONTH
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
setContentView(R.layout.activity_statistics)
|
||||||
|
|
||||||
|
val logbookName = intent.getStringExtra("LOOGBOOK_NAME")
|
||||||
|
if (logbookName == null) {
|
||||||
|
finish()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
noDataTextView = findViewById(R.id.no_data)
|
||||||
|
|
||||||
|
barChart = findViewById(R.id.bar_chart)
|
||||||
|
barChart.setBackgroundColor(Color.WHITE)
|
||||||
|
barChart.description.text = logbookName
|
||||||
|
barChart.setDrawValueAboveBar(false)
|
||||||
|
|
||||||
|
barChart.axisLeft.setAxisMinimum(0F)
|
||||||
|
barChart.axisLeft.setDrawGridLines(false)
|
||||||
|
barChart.axisLeft.setDrawLabels(false)
|
||||||
|
|
||||||
|
barChart.axisRight.setDrawGridLines(false)
|
||||||
|
barChart.axisRight.setDrawLabels(false)
|
||||||
|
|
||||||
|
barChart.xAxis.setDrawGridLines(true)
|
||||||
|
barChart.xAxis.setDrawLabels(true)
|
||||||
|
barChart.xAxis.setDrawAxisLine(false)
|
||||||
|
|
||||||
|
graphTypeSpinner = findViewById(R.id.graph_type_selection)
|
||||||
|
timeRangeSpinner = findViewById(R.id.time_range_selection)
|
||||||
|
|
||||||
|
setupSpinner(graphTypeSelection.name,
|
||||||
|
R.id.graph_type_selection,
|
||||||
|
R.array.StatisticsTypeLabels,
|
||||||
|
R.array.StatisticsTypeValues,
|
||||||
|
object : SpinnerItemSelected {
|
||||||
|
override fun call(newValue: String?) {
|
||||||
|
//Log.d("event", "new value: $newValue")
|
||||||
|
newValue ?: return
|
||||||
|
graphTypeSelection = GraphType.valueOf(newValue)
|
||||||
|
updateGraph()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setupSpinner(timeRangeSelection.name,
|
||||||
|
R.id.time_range_selection,
|
||||||
|
R.array.StatisticsTimeLabels,
|
||||||
|
R.array.StatisticsTimeValues,
|
||||||
|
object : SpinnerItemSelected {
|
||||||
|
override fun call(newValue: String?) {
|
||||||
|
//Log.d("event", "new value: $newValue")
|
||||||
|
newValue ?: return
|
||||||
|
timeRangeSelection = TimeRange.valueOf(newValue)
|
||||||
|
setSpans()
|
||||||
|
updateGraph()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
setSpans()
|
||||||
|
updateGraph()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSpans() {
|
||||||
|
unixToSpan = when (timeRangeSelection) {
|
||||||
|
TimeRange.DAY -> { unix: Long -> unixToDays(unix) }
|
||||||
|
TimeRange.WEEK -> { unix: Long -> unixToWeeks(unix) }
|
||||||
|
TimeRange.MONTH -> { unix: Long -> unixToMonths(unix) }
|
||||||
|
}
|
||||||
|
|
||||||
|
spanToUnix = when (timeRangeSelection) {
|
||||||
|
TimeRange.DAY -> { span: Int -> daysToUnix(span) }
|
||||||
|
TimeRange.WEEK -> { span: Int -> weeksToUnix(span) }
|
||||||
|
TimeRange.MONTH -> { span: Int -> monthsToUnix(span) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showMedicineBarGraph(state: GraphState) {
|
||||||
|
val values = HashMap<String, ArrayList<BarEntry>>()
|
||||||
|
|
||||||
|
for (event in state.events) {
|
||||||
|
val index = unixToSpan(event.time) - state.startSpan
|
||||||
|
val key = event.notes.trim().lowercase()
|
||||||
|
val array = values.getOrPut(key) {
|
||||||
|
ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), 0F) })
|
||||||
|
}
|
||||||
|
|
||||||
|
array[index].y += 1F
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "values.size: ${values.size}")
|
||||||
|
for ((key, value) in values) {
|
||||||
|
Log.d(TAG, "key: $key, value.size: ${value.size} ,value: ${value.joinToString { it.y.toLong().toString() }}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure legend names are not too long
|
||||||
|
fun shorten(notes: String): String {
|
||||||
|
return if (notes.length > 16) {
|
||||||
|
notes.take(13) + "..."
|
||||||
|
} else {
|
||||||
|
notes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun chooseColor(notes: String): Int {
|
||||||
|
return (abs(notes.hashCode()) * 16777215) or (0xFF shl 24)
|
||||||
|
}
|
||||||
|
|
||||||
|
val sets = arrayListOf<IBarDataSet>()
|
||||||
|
for ((key, value) in values.entries) {
|
||||||
|
if (key.startsWith("v")) {
|
||||||
|
val description = shorten(key)
|
||||||
|
val barDataSet = BarDataSet(value, description)
|
||||||
|
barDataSet.color = chooseColor(key)
|
||||||
|
sets.add(barDataSet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val data = BarData(sets)
|
||||||
|
//data.groupBars(0F, 0.2F, 0.1F);
|
||||||
|
data.setValueTextSize(12f)
|
||||||
|
data.barWidth = 1F
|
||||||
|
//data.groupBars(0F, 1F, 1F)
|
||||||
|
|
||||||
|
data.setValueFormatter(object : ValueFormatter() {
|
||||||
|
override fun getFormattedValue(value: Float): String {
|
||||||
|
return if (value == 0F) {
|
||||||
|
""
|
||||||
|
} else {
|
||||||
|
value.toInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
barChart.setScaleEnabled(true)
|
||||||
|
barChart.legend.isEnabled = true
|
||||||
|
|
||||||
|
//barChart.xAxis.setLabelCount(min(values.size, 24), false);
|
||||||
|
//val maxCount = min(maxIndex, 30) // values.size
|
||||||
|
//Log.d(TAG, "maxCount: $maxCount")
|
||||||
|
barChart.setVisibleXRangeMaximum(20F) //maxCount.toFloat()) // show max 24 entries
|
||||||
|
barChart.xAxis.setLabelCount(30, true)
|
||||||
|
//barChart.xAxis.isEnabled = false
|
||||||
|
barChart.xAxis.setCenterAxisLabels(true)
|
||||||
|
barChart.setScaleEnabled(false)
|
||||||
|
|
||||||
|
//barChart.axisLeft.isSLEEP_PATTERN_GRANULARITYEnabled = true
|
||||||
|
//barChart.axisLeft.setSLEEP_PATTERN_GRANULARITY(0.8F)
|
||||||
|
|
||||||
|
barChart.setData(data)
|
||||||
|
barChart.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class SleepRange(val start: Long, var end: Long)
|
||||||
|
|
||||||
|
fun toSleepRanges(events: List<LunaEvent>): ArrayList<SleepRange> {
|
||||||
|
val ranges = arrayListOf<SleepRange>()
|
||||||
|
val now = System.currentTimeMillis() / 1000
|
||||||
|
|
||||||
|
// Transform events into time ranges.
|
||||||
|
// Merge overlapping times and extend
|
||||||
|
// ongoing sleep events until now.
|
||||||
|
var warningShown = false
|
||||||
|
for (event in events) {
|
||||||
|
val startTime = event.time
|
||||||
|
val endTime = if (event.quantity == 0) {
|
||||||
|
now
|
||||||
|
} else {
|
||||||
|
event.time + event.quantity
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle overlap
|
||||||
|
val previousRange = ranges.lastOrNull()
|
||||||
|
if (previousRange != null && previousRange.end > startTime) {
|
||||||
|
// cap previous range to avoid overlap
|
||||||
|
previousRange.end = startTime
|
||||||
|
if (!warningShown) {
|
||||||
|
Toast.makeText(applicationContext, "Overlapping sleep event at ${DateUtils.formatDateTime(startTime)}", Toast.LENGTH_SHORT).show()
|
||||||
|
warningShown = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ranges.add(SleepRange(startTime, endTime))
|
||||||
|
}
|
||||||
|
|
||||||
|
return ranges
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSleepPatternBarGraph(state: GraphState) {
|
||||||
|
val ranges = toSleepRanges(state.events)
|
||||||
|
Log.d(TAG, "startUnix: ${Date(state.startUnix * 1000)}, endUnix: ${Date(state.endUnix * 1000)}")
|
||||||
|
|
||||||
|
val values = ArrayList<BarEntry>()
|
||||||
|
|
||||||
|
val stack = ArrayList(List(state.endSpan - state.startSpan + 1) { IntArray(24 * 60 * 60 / SLEEP_PATTERN_GRANULARITY) })
|
||||||
|
|
||||||
|
Log.d(TAG, "stack.size: ${stack.size}, array.size: ${stack[0].size}, dayCounter.daysWithData.size: ${state.dayCounter.daysWithData.size}")
|
||||||
|
|
||||||
|
fun stackValuePattern(index: Int, spanBegin: Long, spanEnd: Long, begin: Long, end: Long) {
|
||||||
|
val beginDays = unixToDays(begin)
|
||||||
|
val endDays = unixToDays(end)
|
||||||
|
var mid = begin
|
||||||
|
|
||||||
|
//Log.d(TAG, "stackValuePattern: ${beginDays}..${endDays}")
|
||||||
|
|
||||||
|
for (i in beginDays..endDays) {
|
||||||
|
// i is the days/weeks/months since unix epoch
|
||||||
|
val dayBegin = daysToUnix(i)
|
||||||
|
val dayEnd = daysToUnix(i + 1)
|
||||||
|
val sleepBegin = max(mid, dayBegin)
|
||||||
|
val sleepEnd = min(end, dayEnd)
|
||||||
|
|
||||||
|
if (sleepBegin != sleepEnd) {
|
||||||
|
//val index2 = i - spanBegin
|
||||||
|
//val duration = dayEnd - dayBegin
|
||||||
|
assert(dayBegin <= dayEnd)
|
||||||
|
assert(sleepBegin <= sleepEnd)
|
||||||
|
//val duration = sleepEnd - sleepBegin
|
||||||
|
val iBegin = (sleepBegin - dayBegin) / SLEEP_PATTERN_GRANULARITY
|
||||||
|
val iEnd = iBegin + (sleepEnd - sleepBegin) / SLEEP_PATTERN_GRANULARITY
|
||||||
|
Log.d(TAG, "index: $index, iBegin: $iBegin, iEnd: $iEnd, dayBegin: ${Date(dayBegin * 1000)}, dayEnd: ${Date(dayEnd * 1000)}, sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}")
|
||||||
|
for (j in iBegin..iEnd) {
|
||||||
|
stack[index][j.toInt()] += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mid = sleepEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (range in ranges) {
|
||||||
|
// a sleep event can span to another day
|
||||||
|
// distribute sleep time over the days
|
||||||
|
val startUnix = range.start
|
||||||
|
val endUnix = range.end
|
||||||
|
val begIndex = unixToSpan(startUnix)
|
||||||
|
val endIndex = unixToSpan(endUnix)
|
||||||
|
var mid = startUnix
|
||||||
|
|
||||||
|
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
|
||||||
|
for (i in begIndex..endIndex) {
|
||||||
|
// i is the days/weeks/months since unix epoch
|
||||||
|
val spanBegin = spanToUnix(i)
|
||||||
|
val spanEnd = spanToUnix(i + 1)
|
||||||
|
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
|
||||||
|
val sleepBegin = max(mid, spanBegin)
|
||||||
|
val sleepEnd = min(endUnix, spanEnd)
|
||||||
|
val index = i - state.startSpan
|
||||||
|
val duration = sleepEnd - sleepBegin
|
||||||
|
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
|
||||||
|
|
||||||
|
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
|
||||||
|
stackValuePattern(index, spanBegin, spanEnd, sleepBegin, sleepEnd)
|
||||||
|
|
||||||
|
mid = sleepEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun mapColor(occurrences: Int, maxOccurrences: Int): Int {
|
||||||
|
// occurences: number of reported sleeps in a specific time slice
|
||||||
|
// maxOccurrences: maximum number of days with data that can contribute to maxOccurrences
|
||||||
|
assert(maxOccurrences > 0)
|
||||||
|
assert(occurrences <= maxOccurrences)
|
||||||
|
|
||||||
|
// map to color
|
||||||
|
val q = occurrences.toFloat() / maxOccurrences.toFloat()
|
||||||
|
val i = q * (SLEEP_PATTERN_COLORS.size - 1).toFloat()
|
||||||
|
return SLEEP_PATTERN_COLORS[i.toInt()]
|
||||||
|
}
|
||||||
|
|
||||||
|
val allColors = ArrayList<Int>()
|
||||||
|
|
||||||
|
// convert array of time slots that represent a day to value and color arrays used by chart library
|
||||||
|
for ((index, dayArray) in stack.withIndex()) {
|
||||||
|
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
|
||||||
|
|
||||||
|
//Log.d(TAG, "index: $index: dayArray: ${dayArray.joinToString { it.toString() }}")
|
||||||
|
val vals = ArrayList<Float>()
|
||||||
|
|
||||||
|
var prevIndex = -1 // time slice index
|
||||||
|
var prevValue = -1 // number of entries we have found for time slice
|
||||||
|
for ((i, v) in dayArray.withIndex()) {
|
||||||
|
if (i == 0) {
|
||||||
|
prevIndex = i
|
||||||
|
prevValue = v
|
||||||
|
} else if (prevValue != v) {
|
||||||
|
vals.add((i - prevIndex).toFloat())
|
||||||
|
allColors.add(mapColor(prevValue, daysWithData))
|
||||||
|
prevIndex = i
|
||||||
|
prevValue = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevIndex != -1) {
|
||||||
|
vals.add((dayArray.size - prevIndex).toFloat())
|
||||||
|
allColors.add(mapColor(prevValue, daysWithData))
|
||||||
|
}
|
||||||
|
|
||||||
|
//Log.d(TAG, "Range $index, vals: ${vals.joinToString { it.toInt().toString() }}") //, allColors: ${allColors.joinToString { it.toString() }}")
|
||||||
|
|
||||||
|
values.add(BarEntry(index.toFloat(), vals.toFloatArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "daysWithData: ${state.dayCounter.daysWithData.joinToString()}")
|
||||||
|
|
||||||
|
barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
|
||||||
|
override fun onValueSelected(e: Entry?, h: Highlight?) {
|
||||||
|
|
||||||
|
if (e != null && h != null && e.x.toInt() != -1 && h.stackIndex != -1) {
|
||||||
|
if ((lastToastShown + 3500) > System.currentTimeMillis()) {
|
||||||
|
// only show one Toast message after another
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val index = e.x.toInt()
|
||||||
|
val value = values[index]
|
||||||
|
val dayStartUnix = daysToUnix(unixToDays(state.startUnix) + e.x.toInt())
|
||||||
|
|
||||||
|
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, x: ${e.x.toInt()}, dayStartUnix: ${Date(dayStartUnix * 1000)}")
|
||||||
|
val startSeconds = SLEEP_PATTERN_GRANULARITY * value.yVals.sliceArray(0..<h.stackIndex).fold(0) { acc, y -> acc + y.toInt() }
|
||||||
|
val durationSeconds = SLEEP_PATTERN_GRANULARITY * value.yVals[h.stackIndex].toInt()
|
||||||
|
val endSeconds = startSeconds + durationSeconds
|
||||||
|
|
||||||
|
val format = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||||
|
val startTimeString = format.format((dayStartUnix + startSeconds) * 1000).toString()
|
||||||
|
val endTimeString = format.format((dayStartUnix + endSeconds) * 1000).toString()
|
||||||
|
val durationString = NumericUtils(applicationContext).formatEventQuantity(LunaEvent.Type.SLEEP, durationSeconds)
|
||||||
|
|
||||||
|
val daysWithData = stack[e.x.toInt()][startSeconds / SLEEP_PATTERN_GRANULARITY]
|
||||||
|
val daysWithDataMax = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
|
||||||
|
|
||||||
|
// percentage of days in this span where baby is asleep in this time slot
|
||||||
|
val pc = if (daysWithDataMax > 0) {
|
||||||
|
(100F * daysWithData.toFloat() / daysWithDataMax.toFloat()).toInt()
|
||||||
|
} else {
|
||||||
|
// no data for this day
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
Toast.makeText(applicationContext, "$startTimeString - $endTimeString ($durationString) - ${pc}%", Toast.LENGTH_LONG).show()
|
||||||
|
lastToastShown = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected() {}
|
||||||
|
})
|
||||||
|
|
||||||
|
val set1 = BarDataSet(values, "")
|
||||||
|
val data = BarData(set1)
|
||||||
|
set1.colors = allColors
|
||||||
|
//set1.colors = arrayListOf("#00000000".toColorInt(), "#72d7f5".toColorInt())
|
||||||
|
|
||||||
|
set1.setDrawValues(false) // usually too many values
|
||||||
|
set1.isHighlightEnabled = true
|
||||||
|
|
||||||
|
set1.setDrawIcons(false)
|
||||||
|
barChart.legend.isEnabled = false
|
||||||
|
barChart.setScaleEnabled(false)
|
||||||
|
barChart.xAxis.setLabelCount(min(values.size, 24))
|
||||||
|
data.setValueTextSize(12f)
|
||||||
|
barChart.setData(data)
|
||||||
|
|
||||||
|
barChart.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSleepPatternBarGraphDaily(state: GraphState) {
|
||||||
|
val ranges = toSleepRanges(state.events)
|
||||||
|
|
||||||
|
Log.d(TAG, "startUnix: ${Date(state.startUnix * 1000)}, endUnix: ${Date(state.endUnix * 1000)}")
|
||||||
|
|
||||||
|
val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), FloatArray(0)) })
|
||||||
|
|
||||||
|
// stack awake/sleep durations
|
||||||
|
fun stackValuePattern(index: Int, spanBegin: Long, spanEnd: Long, begin: Long, end: Long) {
|
||||||
|
assert(begin in spanBegin..spanEnd)
|
||||||
|
assert(end in spanBegin..spanEnd)
|
||||||
|
assert(begin <= end)
|
||||||
|
|
||||||
|
val x = values[index].x
|
||||||
|
val yVals = values[index].yVals // alternating sleep/awake durations
|
||||||
|
val y = yVals.fold(0F) { acc, next -> acc + next }
|
||||||
|
|
||||||
|
// y value is seconds when last awake
|
||||||
|
val awakeDuration = max(begin - spanBegin - y.toLong(), 0L)
|
||||||
|
val sleepDuration = end - begin
|
||||||
|
|
||||||
|
if ((awakeDuration + sleepDuration) > (spanEnd - spanBegin)) {
|
||||||
|
Log.e(TAG, "Invalid sleep duration, exceeds day/week or month bounds => ignore value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// update value
|
||||||
|
val newYVals = appendToFloatArray(yVals, awakeDuration.toFloat(), sleepDuration.toFloat())
|
||||||
|
values[index] = BarEntry(x, newYVals)
|
||||||
|
}
|
||||||
|
|
||||||
|
for (range in ranges) {
|
||||||
|
// a sleep event can span to another day
|
||||||
|
// distribute sleep time over the days
|
||||||
|
val startUnix = range.start
|
||||||
|
val endUnix = range.end
|
||||||
|
val begIndex = unixToSpan(startUnix)
|
||||||
|
val endIndex = unixToSpan(endUnix)
|
||||||
|
var mid = startUnix
|
||||||
|
|
||||||
|
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
|
||||||
|
for (i in begIndex..endIndex) {
|
||||||
|
// i is the days/weeks/months since unix epoch
|
||||||
|
val spanBegin = spanToUnix(i)
|
||||||
|
val spanEnd = spanToUnix(i + 1)
|
||||||
|
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
|
||||||
|
val sleepBegin = max(mid, spanBegin)
|
||||||
|
val sleepEnd = min(endUnix, spanEnd)
|
||||||
|
val index = i - state.startSpan
|
||||||
|
val duration = sleepEnd - sleepBegin
|
||||||
|
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
|
||||||
|
|
||||||
|
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
|
||||||
|
stackValuePattern(index, spanBegin, spanEnd, sleepBegin, sleepEnd)
|
||||||
|
|
||||||
|
mid = sleepEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val set1 = BarDataSet(values, "")
|
||||||
|
val data = BarData(set1)
|
||||||
|
|
||||||
|
// awake phase color is transparent
|
||||||
|
set1.colors = arrayListOf("#00000000".toColorInt(), "#72d7f5".toColorInt())
|
||||||
|
set1.setDrawValues(false) // usually too many values
|
||||||
|
set1.isHighlightEnabled = true
|
||||||
|
|
||||||
|
//barChart.xAxis.setCenterAxisLabels(true)
|
||||||
|
|
||||||
|
barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
|
||||||
|
override fun onValueSelected(e: Entry?, h: Highlight?) {
|
||||||
|
if (e != null && h != null && e.x.toInt() != -1 && h.stackIndex != -1) {
|
||||||
|
if ((lastToastShown + 3500) > System.currentTimeMillis()) {
|
||||||
|
// only show one Toast message after another
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val value = values[e.x.toInt()]
|
||||||
|
val duration = value.yVals[h.stackIndex].toInt()
|
||||||
|
val durationString = NumericUtils(applicationContext).formatEventQuantity(LunaEvent.Type.SLEEP, duration)
|
||||||
|
|
||||||
|
val offsetUnix = spanToUnix(state.startSpan + e.x.toInt()) // start of the time span (day/week/month)
|
||||||
|
val startUnix = offsetUnix + value.yVals.sliceArray(0..<h.stackIndex).fold(0) { acc, y -> acc + y.toInt() }
|
||||||
|
val endUnix = startUnix + duration
|
||||||
|
|
||||||
|
val format = SimpleDateFormat("HH:mm", Locale.getDefault())
|
||||||
|
val startTimeString = format.format(startUnix * 1000).toString()
|
||||||
|
val endTimeString = format.format(endUnix * 1000).toString()
|
||||||
|
Toast.makeText(applicationContext, "$startTimeString - $endTimeString ($durationString)", Toast.LENGTH_LONG).show()
|
||||||
|
lastToastShown = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected() {}
|
||||||
|
})
|
||||||
|
|
||||||
|
set1.setDrawIcons(false)
|
||||||
|
barChart.legend.isEnabled = false
|
||||||
|
barChart.setScaleEnabled(false)
|
||||||
|
barChart.xAxis.setLabelCount(min(values.size, 24))
|
||||||
|
data.setValueTextSize(12f)
|
||||||
|
barChart.setData(data)
|
||||||
|
|
||||||
|
barChart.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showSleepBarGraph(state: GraphState) {
|
||||||
|
val ranges = toSleepRanges(state.events)
|
||||||
|
val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), 0F) })
|
||||||
|
Log.d(TAG, "startUnix: ${Date(state.startUnix * 1000)}, endUnix: ${Date(state.endUnix * 1000)}")
|
||||||
|
|
||||||
|
|
||||||
|
for (range in ranges) {
|
||||||
|
// a sleep event can span to another day
|
||||||
|
// distribute sleep time over the days
|
||||||
|
val startUnix = range.start
|
||||||
|
val endUnix = range.end
|
||||||
|
val begIndex = unixToSpan(startUnix)
|
||||||
|
val endIndex = unixToSpan(endUnix)
|
||||||
|
var mid = startUnix
|
||||||
|
|
||||||
|
//Log.d(TAG, "startUnix: ${Date(startUnix * 1000)}, endUnix: ${Date(endUnix * 1000)}, begIndex: $begIndex, endIndex: $endIndex (index diff: ${endIndex - begIndex})")
|
||||||
|
for (i in begIndex..endIndex) {
|
||||||
|
// i is the days/weeks/months since unix epoch
|
||||||
|
val spanBegin = spanToUnix(i)
|
||||||
|
val spanEnd = spanToUnix(i + 1)
|
||||||
|
//Log.d(TAG, "mid: ${Date(mid * 1000)}, spanBegin: ${Date(spanBegin * 1000)}, spanEnd: ${Date(spanEnd * 1000)}, beginUnix: ${Date(startUnix * 1000)} endUnix: ${Date(endUnix * 1000)}")
|
||||||
|
val sleepBegin = max(mid, spanBegin)
|
||||||
|
val sleepEnd = min(endUnix, spanEnd)
|
||||||
|
val index = i - state.startSpan
|
||||||
|
val duration = sleepEnd - sleepBegin
|
||||||
|
//Log.d(TAG, "[$index] sleepBegin: ${Date(sleepBegin * 1000)}, sleepEnd: ${Date(sleepEnd * 1000)}, ${DateUtils.formatTimeDuration(this, duration)}")
|
||||||
|
|
||||||
|
state.dayCounter.setDaysWithData(sleepBegin, sleepEnd)
|
||||||
|
|
||||||
|
if (graphTypeSelection == GraphType.SLEEP_SUM) {
|
||||||
|
values[index].y += duration.toFloat()
|
||||||
|
} else if (graphTypeSelection == GraphType.SLEEP_EVENTS) {
|
||||||
|
values[index].y += 1F
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "Unexpected graph type.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
mid = sleepEnd
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphTypeSelection == GraphType.SLEEP_SUM) {
|
||||||
|
for (index in values.indices) {
|
||||||
|
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
|
||||||
|
if (daysWithData == 0) {
|
||||||
|
assert(values[index].y == 0F)
|
||||||
|
} else {
|
||||||
|
values[index].y /= daysWithData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val set1 = BarDataSet(values, "")
|
||||||
|
val data = BarData(set1)
|
||||||
|
set1.setDrawValues(true)
|
||||||
|
set1.isHighlightEnabled = false
|
||||||
|
|
||||||
|
data.setValueFormatter(object : ValueFormatter() {
|
||||||
|
override fun getFormattedValue(value: Float): String {
|
||||||
|
return when (graphTypeSelection) {
|
||||||
|
GraphType.SLEEP_EVENTS -> value.toInt().toString()
|
||||||
|
GraphType.SLEEP_SUM -> {
|
||||||
|
val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "⌀ " }
|
||||||
|
return prefix + NumericUtils(applicationContext).formatEventQuantity(LunaEvent.Type.SLEEP, value.toInt())
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "unhandled graphTypeSelection $graphTypeSelection")
|
||||||
|
value.toInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
set1.setDrawIcons(false)
|
||||||
|
barChart.legend.isEnabled = false
|
||||||
|
barChart.setScaleEnabled(false)
|
||||||
|
barChart.xAxis.setLabelCount(min(values.size, 24))
|
||||||
|
data.setValueTextSize(12f)
|
||||||
|
barChart.setData(data)
|
||||||
|
|
||||||
|
barChart.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showBottleBarGraph(state: GraphState) {
|
||||||
|
val values = ArrayList(List(state.endSpan - state.startSpan + 1) { BarEntry(it.toFloat(), 0F) })
|
||||||
|
|
||||||
|
// needed?
|
||||||
|
for (i in values.indices) {
|
||||||
|
values[i].x = i.toFloat()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (event in state.events) {
|
||||||
|
val index = unixToSpan(event.time) - state.startSpan
|
||||||
|
|
||||||
|
state.dayCounter.setDaysWithData(event.time, event.time)
|
||||||
|
|
||||||
|
// setDaysWithData(sleepBegin, sleepEnd)
|
||||||
|
if (graphTypeSelection == GraphType.BOTTLE_EVENTS) {
|
||||||
|
values[index].y += 1F
|
||||||
|
} else if (graphTypeSelection == GraphType.BOTTLE_SUM) {
|
||||||
|
values[index].y += event.quantity.toFloat()
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, "unhandled graphTypeSelection: $graphTypeSelection")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (graphTypeSelection == GraphType.BOTTLE_SUM) {
|
||||||
|
for (index in values.indices) {
|
||||||
|
val daysWithData = state.dayCounter.countDaysWithData(spanToUnix(state.startSpan + index), spanToUnix(state.startSpan + index + 1))
|
||||||
|
//Log.d(TAG, "index: $index, daysWithData: $daysWithData")
|
||||||
|
if (daysWithData == 0) {
|
||||||
|
assert(values[index].y == 0F)
|
||||||
|
} else {
|
||||||
|
values[index].y /= daysWithData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val set1 = BarDataSet(values, "")
|
||||||
|
|
||||||
|
set1.setDrawValues(true)
|
||||||
|
set1.isHighlightEnabled = false
|
||||||
|
|
||||||
|
val maximumRange = 20F
|
||||||
|
if (graphTypeSelection == GraphType.BOTTLE_SUM || graphTypeSelection == GraphType.BOTTLE_EVENTS) {
|
||||||
|
//val count = values.size.coerceIn(5, 20)
|
||||||
|
barChart.setVisibleXRangeMaximum(maximumRange) // show max 24 entries
|
||||||
|
barChart.xAxis.setLabelCount(maximumRange.toInt(), false)
|
||||||
|
//barChart.xAxis.isEnabled = false
|
||||||
|
barChart.xAxis.setCenterAxisLabels(true)
|
||||||
|
barChart.setScaleEnabled(false)
|
||||||
|
|
||||||
|
//barChart.axisLeft.isSLEEP_PATTERN_GRANULARITYEnabled = true
|
||||||
|
//barChart.axisLeft.setSLEEP_PATTERN_GRANULARITY(0.8F)
|
||||||
|
}
|
||||||
|
|
||||||
|
//val dataSets = ArrayList<IBarDataSet?>()
|
||||||
|
//dataSets.add(set1)
|
||||||
|
|
||||||
|
val data = BarData(set1)
|
||||||
|
//data.barWidth = 0.3F // 0.85 default // ratio of barWidth to totalWidth.
|
||||||
|
//Log.d(TAG, "data.barWidth: ${data.barWidth}")
|
||||||
|
|
||||||
|
data.setValueFormatter(object : ValueFormatter() {
|
||||||
|
override fun getFormattedValue(value: Float): String {
|
||||||
|
//Log.d(TAG, "getFormattedValue ${dataTypeSelectionValue} ${eventTypeSelectionValue}")
|
||||||
|
return when (graphTypeSelection) {
|
||||||
|
GraphType.BOTTLE_EVENTS -> value.toInt().toString()
|
||||||
|
GraphType.BOTTLE_SUM -> {
|
||||||
|
val prefix = if (timeRangeSelection == TimeRange.DAY) { "" } else { "⌀ " }
|
||||||
|
return prefix + NumericUtils(applicationContext).formatEventQuantity(LunaEvent.Type.BABY_BOTTLE, value.toInt())
|
||||||
|
}
|
||||||
|
//GraphType.BOTTLE_SUM_AVERAGE -> "⌀ " + NumericUtils(applicationContext).formatEventQuantity(LunaEvent.TYPE_BABY_BOTTLE, value.toInt())
|
||||||
|
else -> {
|
||||||
|
Log.e(TAG, "unhandled graphTypeSelection")
|
||||||
|
value.toInt().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// hm, does not work yet
|
||||||
|
Log.d(TAG, "last value: ${values.lastOrNull()!!.x}")
|
||||||
|
barChart.moveViewToX(100F)
|
||||||
|
|
||||||
|
data.setValueTextSize(12f)
|
||||||
|
barChart.setData(data)
|
||||||
|
barChart.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DayCounter(val startDays: Int, val stopDays: Int) {
|
||||||
|
val daysWithData = BooleanArray(stopDays - startDays + 1)
|
||||||
|
|
||||||
|
// count days in a span that have data
|
||||||
|
// e.g. return 7 (days) for applied span of a week where there is data for every day
|
||||||
|
fun countDaysWithData(beginUnix: Long, endUnix: Long): Int {
|
||||||
|
val beginDays = unixToDays(beginUnix)
|
||||||
|
val endDays = unixToDays(endUnix)
|
||||||
|
var count = 0
|
||||||
|
for (i in (beginDays - startDays)..<(endDays - startDays)) {
|
||||||
|
count += if (daysWithData[i]) { 1 } else { 0 }
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setDaysWithData(beginUnix: Long, endUnix: Long) {
|
||||||
|
val beginDays = unixToDays(beginUnix)
|
||||||
|
val endDays = unixToDays(endUnix)
|
||||||
|
assert(beginDays <= endDays)
|
||||||
|
assert(startDays <= beginDays)
|
||||||
|
for (i in (beginDays - startDays)..(endDays - startDays)) {
|
||||||
|
daysWithData[i] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class GraphState(val events: List<LunaEvent>, val dayCounter: DayCounter, val startUnix: Long, val endUnix: Long, val startSpan: Int, val endSpan: Int)
|
||||||
|
|
||||||
|
// wrapper for comon graph setup
|
||||||
|
fun prepareGraph(type: LunaEvent.Type, cb: (GraphState) -> Unit) {
|
||||||
|
val events = MainActivity.allEvents.filter { it.type == type }.sortedBy { it.time }
|
||||||
|
|
||||||
|
if (events.isEmpty()) {
|
||||||
|
barChart.visibility = View.GONE
|
||||||
|
noDataTextView.visibility = View.VISIBLE
|
||||||
|
return
|
||||||
|
} else {
|
||||||
|
barChart.visibility = View.VISIBLE
|
||||||
|
noDataTextView.visibility = View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
// unix time span of all events
|
||||||
|
val startUnix = events.minOf { it.time }
|
||||||
|
val endUnix = System.currentTimeMillis() / 1000
|
||||||
|
|
||||||
|
// convert to days, weeks or months
|
||||||
|
val startSpan = unixToSpan(startUnix)
|
||||||
|
val endSpan = unixToSpan(endUnix)
|
||||||
|
|
||||||
|
// days when the a day/week/month starts/ends
|
||||||
|
val startDays = unixToDays(spanToUnix(startSpan))
|
||||||
|
val endDays = unixToDays(spanToUnix(endSpan + 1)) // until end of next week
|
||||||
|
val dayCounter = DayCounter(startDays, endDays)
|
||||||
|
|
||||||
|
// print dates
|
||||||
|
barChart.xAxis.valueFormatter = object: ValueFormatter() {
|
||||||
|
override fun getFormattedValue(value: Float): String {
|
||||||
|
val index = value.toInt()
|
||||||
|
val unixSeconds = spanToUnix(startSpan + index)
|
||||||
|
|
||||||
|
val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(1000L * unixSeconds)
|
||||||
|
val year = dateTime.get(Calendar.YEAR)
|
||||||
|
val month = dateTime.get(Calendar.MONTH) + 1 // month starts at 0
|
||||||
|
val week = dateTime.get(Calendar.WEEK_OF_YEAR)
|
||||||
|
val day = dateTime.get(Calendar.DAY_OF_MONTH)
|
||||||
|
return when (timeRangeSelection) {
|
||||||
|
TimeRange.DAY -> "$day/$month/$year"
|
||||||
|
TimeRange.WEEK -> "$week/$year"
|
||||||
|
TimeRange.MONTH -> "$month/$year"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.d(TAG, "startDaysUnix: ${Date(daysToUnix(startDays) * 1000)}. endDaysUnix: ${Date(daysToUnix(endDays) * 1000)}")
|
||||||
|
|
||||||
|
cb(GraphState(events, dayCounter, startUnix, endUnix, startSpan, endSpan))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateGraph() {
|
||||||
|
Log.d(TAG, "updateGraph: graphTypeSelection: $graphTypeSelection, timeRangeSelection: $timeRangeSelection")
|
||||||
|
|
||||||
|
when (graphTypeSelection) {
|
||||||
|
GraphType.BOTTLE_EVENTS,
|
||||||
|
GraphType.BOTTLE_SUM -> prepareGraph(LunaEvent.Type.BABY_BOTTLE) { state -> showBottleBarGraph(state) }
|
||||||
|
GraphType.SLEEP_EVENTS,
|
||||||
|
GraphType.SLEEP_SUM -> prepareGraph(LunaEvent.Type.SLEEP) { state -> showSleepBarGraph(state) }
|
||||||
|
GraphType.SLEEP_PATTERN -> prepareGraph(LunaEvent.Type.SLEEP) { state -> showSleepPatternBarGraph(state) }
|
||||||
|
GraphType.MEDICINE_EVENTS -> prepareGraph(LunaEvent.Type.MEDICINE) { state -> showMedicineBarGraph(state) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface SpinnerItemSelected {
|
||||||
|
fun call(newValue: String?)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupSpinner(
|
||||||
|
currentValue: String,
|
||||||
|
spinnerId: Int,
|
||||||
|
arrayId: Int,
|
||||||
|
arrayValuesId: Int,
|
||||||
|
callback: SpinnerItemSelected
|
||||||
|
) {
|
||||||
|
val arrayValues = resources.getStringArray(arrayValuesId)
|
||||||
|
val spinner = findViewById<Spinner>(spinnerId)
|
||||||
|
val spinnerAdapter =
|
||||||
|
ArrayAdapter.createFromResource(this, arrayId, R.layout.statistics_spinner_item)
|
||||||
|
|
||||||
|
//Log.d(TAG, "spinner ${arrayValues.indexOf(currentValue)} (${arrayValues.joinToString { it }})")
|
||||||
|
|
||||||
|
spinner.adapter = spinnerAdapter
|
||||||
|
spinner.setSelection(arrayValues.indexOf(currentValue))
|
||||||
|
spinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||||
|
var check = 0
|
||||||
|
override fun onItemSelected(parent: AdapterView<*>?, view: View?, pos: Int, id: Long) {
|
||||||
|
if (pos >= arrayValues.size) {
|
||||||
|
Toast.makeText(
|
||||||
|
this@StatisticsActivity,
|
||||||
|
"pos out of bounds: $arrayValues", Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (check++ > 0) {
|
||||||
|
callback.call(arrayValues[pos])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onNothingSelected(parent: AdapterView<*>?) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "StatisticsActivity"
|
||||||
|
|
||||||
|
// 15 min steps
|
||||||
|
val SLEEP_PATTERN_GRANULARITY = 15 * 60
|
||||||
|
|
||||||
|
// color gradient
|
||||||
|
val SLEEP_PATTERN_COLORS = arrayOf(
|
||||||
|
"#FFFFFF".toColorInt(), "#EEF5F7".toColorInt(), "#DDEBEF".toColorInt(), "#CCE2E7".toColorInt(),
|
||||||
|
"#BBD8DF".toColorInt(), "#AACED7".toColorInt(), "#99C4CF".toColorInt(), "#88BAC7".toColorInt(),
|
||||||
|
"#77B1BF".toColorInt(), "#66A7B7".toColorInt(), "#559DAF".toColorInt(), "#4493A7".toColorInt(),
|
||||||
|
"#33899F".toColorInt(), "#228097".toColorInt(), "#11768F".toColorInt(), "#006C87".toColorInt()
|
||||||
|
)
|
||||||
|
private val dateTime = Calendar.getInstance() // scratch pad
|
||||||
|
var graphTypeSelection = GraphType.SLEEP_SUM
|
||||||
|
var timeRangeSelection = TimeRange.DAY
|
||||||
|
|
||||||
|
fun unixToMonths(seconds: Long): Int {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(seconds * 1000)
|
||||||
|
val years = dateTime.get(Calendar.YEAR)
|
||||||
|
val months = dateTime.get(Calendar.MONTH)
|
||||||
|
return 12 * years + months
|
||||||
|
}
|
||||||
|
|
||||||
|
fun monthsToUnix(months: Int): Long {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(0)
|
||||||
|
dateTime.set(Calendar.YEAR, months / 12)
|
||||||
|
dateTime.set(Calendar.MONTH, months % 12)
|
||||||
|
dateTime.set(Calendar.HOUR, 0)
|
||||||
|
dateTime.set(Calendar.MINUTE, 0)
|
||||||
|
dateTime.set(Calendar.SECOND, 0)
|
||||||
|
return dateTime.time.time / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unixToWeeks(seconds: Long): Int {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(seconds * 1000)
|
||||||
|
val years = dateTime.get(Calendar.YEAR)
|
||||||
|
val weeks = dateTime.get(Calendar.WEEK_OF_YEAR)
|
||||||
|
return 52 * years + weeks
|
||||||
|
}
|
||||||
|
|
||||||
|
fun weeksToUnix(weeks: Int): Long {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(0)
|
||||||
|
dateTime.set(Calendar.YEAR, weeks / 52)
|
||||||
|
dateTime.set(Calendar.WEEK_OF_YEAR, weeks % 52)
|
||||||
|
dateTime.set(Calendar.DAY_OF_WEEK, Calendar.MONDAY)
|
||||||
|
dateTime.set(Calendar.HOUR, 0)
|
||||||
|
dateTime.set(Calendar.MINUTE, 0)
|
||||||
|
dateTime.set(Calendar.SECOND, 0)
|
||||||
|
return dateTime.time.time / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unixToDays(seconds: Long): Int {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(seconds * 1000)
|
||||||
|
val years = dateTime.get(Calendar.YEAR)
|
||||||
|
val days = dateTime.get(Calendar.DAY_OF_YEAR)
|
||||||
|
return 365 * years + days
|
||||||
|
}
|
||||||
|
|
||||||
|
// convert from days to Date
|
||||||
|
fun daysToUnix(days: Int): Long {
|
||||||
|
//val dateTime = Calendar.getInstance()
|
||||||
|
dateTime.time = Date(0)
|
||||||
|
dateTime.set(Calendar.YEAR, days / 365)
|
||||||
|
dateTime.set(Calendar.DAY_OF_YEAR, days % 365)
|
||||||
|
dateTime.set(Calendar.HOUR, 0)
|
||||||
|
dateTime.set(Calendar.MINUTE, 0)
|
||||||
|
dateTime.set(Calendar.SECOND, 0)
|
||||||
|
return dateTime.time.time / 1000
|
||||||
|
}
|
||||||
|
|
||||||
|
fun appendToFloatArray(array: FloatArray, vararg values: Float): FloatArray {
|
||||||
|
// create new array
|
||||||
|
val newArray = FloatArray(array.size + values.size)
|
||||||
|
// copy old values
|
||||||
|
for (i in array.indices) {
|
||||||
|
newArray[i] = array[i]
|
||||||
|
}
|
||||||
|
// add new values
|
||||||
|
for (i in values.indices) {
|
||||||
|
newArray[array.size + i] = values[i]
|
||||||
|
}
|
||||||
|
return newArray
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
fun colorGradient(fromColor: Int, toColor: Int, percent: Int): Int {
|
||||||
|
assert(percent in 0..100)
|
||||||
|
|
||||||
|
val a1 = fromColor.shr(24).and(0xff)
|
||||||
|
val r1 = fromColor.shr(16).and(0xff)
|
||||||
|
val g1 = fromColor.shr(8).and(0xff)
|
||||||
|
val b1 = fromColor.shr(0).and(0xff)
|
||||||
|
|
||||||
|
//Log.d(TAG, "${a1.toHexString()} ${r1.toHexString()} ${g1.toHexString()} ${b1.toHexString()}")
|
||||||
|
|
||||||
|
val a2 = toColor.shr(24).and(0xff)
|
||||||
|
val r2 = toColor.shr(16).and(0xff)
|
||||||
|
val g2 = toColor.shr(8).and(0xff)
|
||||||
|
val b2 = toColor.shr(0).and(0xff)
|
||||||
|
|
||||||
|
//Log.d(TAG, "${a2.toHexString()} ${r2.toHexString()} ${g2.toHexString()} ${b2.toHexString()}")
|
||||||
|
|
||||||
|
val pc = (percent.toFloat() / 100F).coerceIn(0F, 1F)
|
||||||
|
val a = a1 + (pc * abs(a2 - a1)).toInt()
|
||||||
|
val r = r1 + (pc * abs(r2 - r1)).toInt()
|
||||||
|
val g = g1 + (pc * abs(g2 - g1)).toInt()
|
||||||
|
val b = a1 + (pc * abs(b2 - b1)).toInt()
|
||||||
|
|
||||||
|
//Log.d(TAG, "${a.toHexString()} ${r.toHexString()} ${g.toHexString()} ${b.toHexString()}")
|
||||||
|
val Red = r.shl(16).and(0x00FF0000)
|
||||||
|
val Green = g.shl(8).and(0x0000FF00)
|
||||||
|
val Blue = b.and(0x000000FF)
|
||||||
|
val aa = a.shl(24).and(0xFF000000.toInt())
|
||||||
|
val color = aa.or(Red).or(Green).or(Blue)
|
||||||
|
return color
|
||||||
|
//Log.d(TAG, "c: ${c.toHexString()} ${color.toInt().toHexString()}")
|
||||||
|
//return Color.argb(a, r, g, b)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
// for debugging
|
||||||
|
fun debugBarValues(values: ArrayList<BarEntry>) {
|
||||||
|
for (value in values) {
|
||||||
|
val yVals = value.yVals
|
||||||
|
if (yVals != null) {
|
||||||
|
val y = yVals.fold(0F) { acc, next -> acc + next }
|
||||||
|
val yVals = yVals.joinToString { it.toString() }
|
||||||
|
Log.d(TAG, "value: ${value.x} $y ($yVals)")
|
||||||
|
} else {
|
||||||
|
Log.d(TAG, "value: ${value.x} ${value.y}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -52,18 +52,22 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
|
|||||||
)
|
)
|
||||||
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
|
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
|
||||||
// Contents
|
// Contents
|
||||||
holder.type.text = item.getTypeEmoji(context)
|
holder.type.text = item.getHeaderEmoji(context)
|
||||||
holder.description.text = when (item.type) {
|
holder.description.text = when (item.type) {
|
||||||
LunaEvent.TYPE_MEDICINE -> item.notes
|
LunaEvent.Type.MEDICINE -> item.notes
|
||||||
LunaEvent.TYPE_NOTE -> item.notes
|
LunaEvent.Type.NOTE -> item.notes
|
||||||
LunaEvent.TYPE_CUSTOM -> item.notes
|
else -> item.getRowItemTitle(context)
|
||||||
else -> item.getTypeDescription(context)
|
|
||||||
}
|
}
|
||||||
holder.time.text = DateUtils.formatTimeAgo(context, item.time)
|
val endTime = if (item.type == LunaEvent.Type.SLEEP) {
|
||||||
|
item.quantity + item.time
|
||||||
|
} else {
|
||||||
|
item.time
|
||||||
|
}
|
||||||
|
holder.time.text = DateUtils.formatTimeAgo(context, endTime)
|
||||||
var quantityText = numericUtils.formatEventQuantity(item)
|
var quantityText = numericUtils.formatEventQuantity(item)
|
||||||
|
|
||||||
// if the event is weight, show difference with the last one
|
// if the event is weight, show difference with the last one
|
||||||
if (item.type == LunaEvent.TYPE_WEIGHT) {
|
if (item.type == LunaEvent.Type.WEIGHT) {
|
||||||
val lastWeight = getPreviousWeightEvent(position)
|
val lastWeight = getPreviousWeightEvent(position)
|
||||||
if (lastWeight != null) {
|
if (lastWeight != null) {
|
||||||
val differenceInWeight = item.quantity - lastWeight.quantity
|
val differenceInWeight = item.quantity - lastWeight.quantity
|
||||||
@@ -94,7 +98,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
|
|||||||
return null
|
return null
|
||||||
for (pos in startFromPosition + 1 until items.size) {
|
for (pos in startFromPosition + 1 until items.size) {
|
||||||
val item = items.get(pos)
|
val item = items.get(pos)
|
||||||
if (item.type != LunaEvent.TYPE_WEIGHT)
|
if (item.type != LunaEvent.Type.WEIGHT)
|
||||||
continue
|
continue
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,24 +12,24 @@ import java.util.Date
|
|||||||
* release, it is simply ignored by previous ones).
|
* release, it is simply ignored by previous ones).
|
||||||
*/
|
*/
|
||||||
class LunaEvent: Comparable<LunaEvent> {
|
class LunaEvent: Comparable<LunaEvent> {
|
||||||
|
enum class Type {
|
||||||
companion object {
|
BABY_BOTTLE,
|
||||||
const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
|
FOOD,
|
||||||
const val TYPE_WEIGHT = "WEIGHT"
|
BREASTFEEDING_LEFT_NIPPLE,
|
||||||
const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
|
BREASTFEEDING_BOTH_NIPPLE,
|
||||||
const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
|
BREASTFEEDING_RIGHT_NIPPLE,
|
||||||
const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
|
DIAPERCHANGE_POO,
|
||||||
const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
|
DIAPERCHANGE_PEE,
|
||||||
const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
|
SLEEP,
|
||||||
const val TYPE_MEDICINE = "MEDICINE"
|
WEIGHT,
|
||||||
const val TYPE_ENEMA = "ENEMA"
|
MEDICINE,
|
||||||
const val TYPE_NOTE = "NOTE"
|
ENEMA,
|
||||||
const val TYPE_CUSTOM = "CUSTOM"
|
NOTE,
|
||||||
const val TYPE_COLIC = "COLIC"
|
COLIC,
|
||||||
const val TYPE_TEMPERATURE = "TEMPERATURE"
|
TEMPERATURE,
|
||||||
const val TYPE_FOOD = "FOOD"
|
PUKE,
|
||||||
const val TYPE_PUKE = "PUKE"
|
BATH,
|
||||||
const val TYPE_BATH = "BATH"
|
UNKNOWN
|
||||||
}
|
}
|
||||||
|
|
||||||
private val jo: JSONObject
|
private val jo: JSONObject
|
||||||
@@ -39,16 +39,24 @@ class LunaEvent: Comparable<LunaEvent> {
|
|||||||
set(value) {
|
set(value) {
|
||||||
jo.put("time", value)
|
jo.put("time", value)
|
||||||
}
|
}
|
||||||
var type: String
|
var type: Type
|
||||||
get(): String = jo.getString("type")
|
get(): Type {
|
||||||
|
return try {
|
||||||
|
Type.valueOf(jo.getString("type"))
|
||||||
|
} catch (_: Exception) {
|
||||||
|
Type.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
jo.put("type", value)
|
jo.put("type", value.name)
|
||||||
}
|
}
|
||||||
var quantity: Int
|
var quantity: Int
|
||||||
get() = jo.optInt("quantity")
|
get() = jo.optInt("quantity")
|
||||||
set(value) {
|
set(value) {
|
||||||
if (value > 0)
|
if (value > 0)
|
||||||
jo.put("quantity", value)
|
jo.put("quantity", value)
|
||||||
|
else
|
||||||
|
jo.remove("quantity")
|
||||||
}
|
}
|
||||||
var notes: String
|
var notes: String
|
||||||
get(): String = jo.optString("notes")
|
get(): String = jo.optString("notes")
|
||||||
@@ -69,70 +77,42 @@ class LunaEvent: Comparable<LunaEvent> {
|
|||||||
throw IllegalArgumentException("JSONObject is not a LunaEvent")
|
throw IllegalArgumentException("JSONObject is not a LunaEvent")
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(type: String) {
|
constructor(event: LunaEvent) {
|
||||||
|
this.jo = JSONObject()
|
||||||
|
this.type = event.type
|
||||||
|
this.time = event.time
|
||||||
|
this.quantity = event.quantity
|
||||||
|
this.notes = event.notes
|
||||||
|
this.signature = event.signature
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(type: Type) {
|
||||||
this.jo = JSONObject()
|
this.jo = JSONObject()
|
||||||
this.time = System.currentTimeMillis() / 1000
|
this.time = System.currentTimeMillis() / 1000
|
||||||
this.type = type
|
this.type = type
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(type: String, quantity: Int) {
|
constructor(type: Type, quantity: Int) {
|
||||||
this.jo = JSONObject()
|
this.jo = JSONObject()
|
||||||
this.time = System.currentTimeMillis() / 1000
|
this.time = System.currentTimeMillis() / 1000
|
||||||
this.type = type
|
this.type = type
|
||||||
this.quantity = quantity
|
this.quantity = quantity
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTypeEmoji(context: Context): String {
|
fun getHeaderEmoji(context: Context): String {
|
||||||
return context.getString(
|
return getHeaderEmoji(context, type)
|
||||||
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
|
|
||||||
else -> R.string.event_unknown_type
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getTypeDescription(context: Context): String {
|
fun getDialogTitle(context: Context): String {
|
||||||
return context.getString(
|
return getDialogTitle(context, type)
|
||||||
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
|
|
||||||
else -> R.string.event_unknown_desc
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getDialogMessage(context: Context): String? {
|
fun getRowItemTitle(context: Context): String {
|
||||||
return when(type) {
|
return getPopupItemTitle(context, type).split(" ", limit = 2).last() // remove emoji
|
||||||
TYPE_MEDICINE -> context.getString(R.string.log_medicine_dialog_description)
|
}
|
||||||
else -> null
|
|
||||||
}
|
fun getDialogMessage(context: Context): String {
|
||||||
|
return getDialogMessage(context, type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun toJson(): JSONObject {
|
fun toJson(): JSONObject {
|
||||||
@@ -146,4 +126,95 @@ class LunaEvent: Comparable<LunaEvent> {
|
|||||||
override fun compareTo(other: LunaEvent): Int {
|
override fun compareTo(other: LunaEvent): Int {
|
||||||
return (this.time - other.time).toInt()
|
return (this.time - other.time).toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun getHeaderEmoji(context: Context, type: Type): String {
|
||||||
|
return context.getString(
|
||||||
|
when (type) {
|
||||||
|
Type.BABY_BOTTLE -> R.string.event_bottle_type
|
||||||
|
Type.WEIGHT -> R.string.event_weight_type
|
||||||
|
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_type
|
||||||
|
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_type
|
||||||
|
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_type
|
||||||
|
Type.DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_type
|
||||||
|
Type.DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_type
|
||||||
|
Type.MEDICINE -> R.string.event_medicine_type
|
||||||
|
Type.ENEMA -> R.string.event_enema_type
|
||||||
|
Type.NOTE -> R.string.event_note_type
|
||||||
|
Type.TEMPERATURE -> R.string.event_temperature_type
|
||||||
|
Type.COLIC -> R.string.event_colic_type
|
||||||
|
Type.FOOD -> R.string.event_food_type
|
||||||
|
Type.PUKE -> R.string.event_puke_type
|
||||||
|
Type.BATH -> R.string.event_bath_type
|
||||||
|
Type.SLEEP -> R.string.event_sleep_type
|
||||||
|
Type.UNKNOWN -> R.string.event_unknown_type
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDialogMessage(context: Context, type: Type): String {
|
||||||
|
return context.getString(
|
||||||
|
when (type) {
|
||||||
|
Type.BABY_BOTTLE -> R.string.log_bottle_dialog_description
|
||||||
|
Type.MEDICINE -> R.string.log_medicine_dialog_description
|
||||||
|
Type.TEMPERATURE -> R.string.log_temperature_dialog_description
|
||||||
|
Type.DIAPERCHANGE_POO,
|
||||||
|
Type.DIAPERCHANGE_PEE,
|
||||||
|
Type.PUKE -> R.string.log_amount_dialog_description
|
||||||
|
Type.WEIGHT -> R.string.log_weight_dialog_description
|
||||||
|
Type.SLEEP -> R.string.log_sleep_dialog_description
|
||||||
|
else -> R.string.log_unknown_dialog_description
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDialogTitle(context: Context, type: Type): String {
|
||||||
|
return context.getString(
|
||||||
|
when (type) {
|
||||||
|
Type.BABY_BOTTLE -> R.string.event_bottle_desc
|
||||||
|
Type.WEIGHT -> R.string.event_weight_desc
|
||||||
|
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_breastfeeding_left_desc
|
||||||
|
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_breastfeeding_both_desc
|
||||||
|
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_breastfeeding_right_desc
|
||||||
|
Type.DIAPERCHANGE_POO -> R.string.event_diaperchange_poo_desc
|
||||||
|
Type.DIAPERCHANGE_PEE -> R.string.event_diaperchange_pee_desc
|
||||||
|
Type.MEDICINE -> R.string.event_medicine_desc
|
||||||
|
Type.ENEMA -> R.string.event_enema_desc
|
||||||
|
Type.NOTE -> R.string.event_note_desc
|
||||||
|
Type.TEMPERATURE -> R.string.event_temperature_desc
|
||||||
|
Type.COLIC -> R.string.event_colic_desc
|
||||||
|
Type.FOOD -> R.string.event_food_desc
|
||||||
|
Type.PUKE -> R.string.event_puke_desc
|
||||||
|
Type.BATH -> R.string.event_bath_desc
|
||||||
|
Type.SLEEP -> R.string.event_sleep_desc
|
||||||
|
Type.UNKNOWN -> R.string.event_unknown_desc
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entries for for popup list
|
||||||
|
fun getPopupItemTitle(context: Context, type: Type): String {
|
||||||
|
return context.getString(
|
||||||
|
when (type) {
|
||||||
|
Type.BABY_BOTTLE -> R.string.event_type_item_bottle
|
||||||
|
Type.WEIGHT -> R.string.event_type_item_weight
|
||||||
|
Type.BREASTFEEDING_LEFT_NIPPLE -> R.string.event_type_item_breastfeeding_left
|
||||||
|
Type.BREASTFEEDING_BOTH_NIPPLE -> R.string.event_type_item_breastfeeding_both
|
||||||
|
Type.BREASTFEEDING_RIGHT_NIPPLE -> R.string.event_type_item_breastfeeding_right
|
||||||
|
Type.DIAPERCHANGE_POO -> R.string.event_type_item_diaperchange_poo
|
||||||
|
Type.DIAPERCHANGE_PEE -> R.string.event_type_item_diaperchange_pee
|
||||||
|
Type.MEDICINE -> R.string.event_type_item_medicine
|
||||||
|
Type.ENEMA -> R.string.event_type_item_enema
|
||||||
|
Type.NOTE -> R.string.event_type_item_note
|
||||||
|
Type.TEMPERATURE -> R.string.event_type_item_temperature
|
||||||
|
Type.COLIC -> R.string.event_type_item_colic
|
||||||
|
Type.FOOD -> R.string.event_type_item_food
|
||||||
|
Type.PUKE -> R.string.event_type_item_puke
|
||||||
|
Type.BATH -> R.string.event_type_item_bath
|
||||||
|
Type.SLEEP -> R.string.event_type_item_sleep
|
||||||
|
Type.UNKNOWN -> R.string.event_type_item_unknown
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
const val SHARED_PREFS_DAV_URL = "webdav_url"
|
const val SHARED_PREFS_DAV_URL = "webdav_url"
|
||||||
const val SHARED_PREFS_DAV_USER = "webdav_user"
|
const val SHARED_PREFS_DAV_USER = "webdav_user"
|
||||||
const val SHARED_PREFS_DAV_PASS = "webdav_password"
|
const val SHARED_PREFS_DAV_PASS = "webdav_password"
|
||||||
const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
|
const val SHARED_PREFS_DYNAMIC_MENU = "dynamic_menu"
|
||||||
const val SHARED_PREFS_SIGNATURE = "signature"
|
const val SHARED_PREFS_SIGNATURE = "signature"
|
||||||
}
|
}
|
||||||
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
|
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
|
||||||
@@ -23,14 +23,6 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
sharedPreferences = context.getSharedPreferences(SHARED_PREFS_FILE_NAME, MODE_PRIVATE)
|
sharedPreferences = context.getSharedPreferences(SHARED_PREFS_FILE_NAME, MODE_PRIVATE)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveBabyBottleContent(content: Int) {
|
|
||||||
sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadBabyBottleContent(): Int {
|
|
||||||
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun saveSignature(content: String) {
|
fun saveSignature(content: String) {
|
||||||
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
|
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
|
||||||
}
|
}
|
||||||
@@ -39,12 +31,12 @@ class LocalSettingsRepository(val context: Context) {
|
|||||||
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
|
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveNoBreastfeeding(content: Boolean) {
|
fun saveDynamicMenu(content: Boolean) {
|
||||||
sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
|
sharedPreferences.edit { putBoolean(SHARED_PREFS_DYNAMIC_MENU, content) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadNoBreastfeeding(): Boolean {
|
fun loadDynamicMenu(): Boolean {
|
||||||
return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false)
|
return sharedPreferences.getBoolean(SHARED_PREFS_DYNAMIC_MENU, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveDataRepository(repo: DATA_REPO) {
|
fun saveDataRepository(repo: DATA_REPO) {
|
||||||
|
|||||||
@@ -62,10 +62,8 @@ class DateUtils {
|
|||||||
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
|
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
|
||||||
} else if (hours > 0) {
|
} else if (hours > 0) {
|
||||||
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
|
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 {
|
} else {
|
||||||
return context.getString(R.string.now)
|
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import android.content.Context
|
|||||||
import android.icu.util.LocaleData
|
import android.icu.util.LocaleData
|
||||||
import android.icu.util.ULocale
|
import android.icu.util.ULocale
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.util.Log
|
||||||
import it.danieleverducci.lunatracker.R
|
import it.danieleverducci.lunatracker.R
|
||||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||||
|
import utils.DateUtils.Companion.formatTimeDuration
|
||||||
import java.text.NumberFormat
|
import java.text.NumberFormat
|
||||||
|
|
||||||
class NumericUtils (val context: Context) {
|
class NumericUtils (val context: Context) {
|
||||||
@@ -60,39 +62,55 @@ class NumericUtils (val context: Context) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun formatEventQuantity(item: LunaEvent): String {
|
fun formatEventQuantity(event: LunaEvent): String {
|
||||||
|
return formatEventQuantity(event.type, event.quantity)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun formatEventQuantity(type: LunaEvent.Type, quantity: Int): String {
|
||||||
val formatted = StringBuilder()
|
val formatted = StringBuilder()
|
||||||
if (item.quantity > 0) {
|
if (quantity > 0) {
|
||||||
formatted.append(when (item.type) {
|
formatted.append(when (type) {
|
||||||
LunaEvent.TYPE_TEMPERATURE ->
|
LunaEvent.Type.TEMPERATURE ->
|
||||||
(item.quantity / 10.0f).toString()
|
(quantity / 10.0f).toString()
|
||||||
LunaEvent.TYPE_PUKE ->
|
LunaEvent.Type.DIAPERCHANGE_POO,
|
||||||
context.resources.getStringArray(R.array.AmountLabels)[item.quantity - 1]
|
LunaEvent.Type.DIAPERCHANGE_PEE,
|
||||||
else ->
|
LunaEvent.Type.PUKE -> {
|
||||||
item.quantity
|
val array = context.resources.getStringArray(R.array.AmountLabels)
|
||||||
|
return array.getOrElse(quantity) {
|
||||||
|
Log.e("NumericUtils", "Invalid index $quantity")
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LunaEvent.Type.SLEEP -> formatTimeDuration(context, quantity.toLong())
|
||||||
|
else -> quantity
|
||||||
})
|
})
|
||||||
|
|
||||||
formatted.append(" ")
|
formatted.append(" ")
|
||||||
formatted.append(
|
formatted.append(
|
||||||
when (item.type) {
|
when (type) {
|
||||||
LunaEvent.TYPE_BABY_BOTTLE -> measurement_unit_liquid_base
|
LunaEvent.Type.BABY_BOTTLE -> measurement_unit_liquid_base
|
||||||
LunaEvent.TYPE_WEIGHT -> measurement_unit_weight_base
|
LunaEvent.Type.WEIGHT -> measurement_unit_weight_base
|
||||||
LunaEvent.TYPE_MEDICINE -> measurement_unit_weight_tiny
|
LunaEvent.Type.MEDICINE -> measurement_unit_weight_tiny
|
||||||
LunaEvent.TYPE_TEMPERATURE -> measurement_unit_temperature_base
|
LunaEvent.Type.TEMPERATURE -> measurement_unit_temperature_base
|
||||||
else -> ""
|
else -> ""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
formatted.append(when (type) {
|
||||||
|
LunaEvent.Type.SLEEP -> "💤" // baby is sleeping
|
||||||
|
else -> ""
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return formatted.toString()
|
return formatted.toString().trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a valid quantity range for the event type.
|
* Returns a valid quantity range for the event type.
|
||||||
* @return min, max, normal
|
* @return min, max, normal
|
||||||
*/
|
*/
|
||||||
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
|
fun getValidEventQuantityRange(lunaEventType: LunaEvent.Type): Triple<Int, Int, Int>? {
|
||||||
return when (lunaEventType) {
|
return when (lunaEventType) {
|
||||||
LunaEvent.TYPE_TEMPERATURE -> {
|
LunaEvent.Type.TEMPERATURE -> {
|
||||||
if (isMetricSystem())
|
if (isMetricSystem())
|
||||||
Triple(
|
Triple(
|
||||||
context.resources.getInteger(R.integer.human_body_temp_min_metric),
|
context.resources.getInteger(R.integer.human_body_temp_min_metric),
|
||||||
|
|||||||
@@ -86,15 +86,16 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_layout_row1"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_bottle"
|
android:id="@+id/button1_row1"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="2"
|
android:layout_weight="1"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
@@ -102,7 +103,7 @@
|
|||||||
android:text="@string/event_bottle_type"/>
|
android:text="@string/event_bottle_type"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_food"
|
android:id="@+id/button2_row1"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
@@ -115,77 +116,73 @@
|
|||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/linear_layout_row2"
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:id="@+id/layout_nipples">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/button_nipple_left"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@drawable/button_background"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:textSize="30sp"
|
|
||||||
android:text="🤱⬅️"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/button_nipple_both"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@drawable/button_background"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:textSize="30sp"
|
|
||||||
android:text="🤱↔️"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/button_nipple_right"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="5dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:background="@drawable/button_background"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:textSize="30sp"
|
|
||||||
android:text="🤱➡️️"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_change_poo"
|
android:id="@+id/button1_row2"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="2"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"/>
|
||||||
android:text="🚼 💩"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_change_pee"
|
android:id="@+id/button2_row2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="30sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button3_row2"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="30sp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/linear_layout_row3"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button1_row3"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="2"
|
android:layout_weight="2"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"/>
|
||||||
android:text="🚼 💧"/>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/button2_row3"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_margin="5dp"
|
||||||
|
android:layout_weight="2"
|
||||||
|
android:background="@drawable/button_background"
|
||||||
|
android:gravity="center_horizontal"
|
||||||
|
android:textSize="30sp"/>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
android:id="@+id/button_more"
|
android:id="@+id/button_more"
|
||||||
android:layout_width="0dp"
|
android:layout_width="60dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_margin="5dp"
|
android:layout_margin="5dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="0"
|
||||||
android:background="@drawable/button_background"
|
android:background="@drawable/button_background"
|
||||||
android:gravity="center_horizontal"
|
android:gravity="center_horizontal"
|
||||||
android:src="@drawable/ic_more"
|
android:src="@drawable/ic_more"
|
||||||
|
|||||||
@@ -137,7 +137,7 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="30dp"
|
android:layout_marginStart="20dp"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:text="@string/settings_signature_desc"/>
|
android:text="@string/settings_signature_desc"/>
|
||||||
|
|
||||||
@@ -149,13 +149,13 @@
|
|||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
android:text="@string/settings_no_breastfeeding" />
|
android:text="@string/settings_dynamic_menu" />
|
||||||
|
|
||||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
android:id="@+id/switch_no_breastfeeding"
|
android:id="@+id/switch_dynamic_menu"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="20dp"
|
android:layout_marginEnd="20dp"
|
||||||
@@ -166,9 +166,9 @@
|
|||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="30dp"
|
android:layout_marginStart="20dp"
|
||||||
android:layout_marginTop="5dp"
|
android:layout_marginTop="5dp"
|
||||||
android:text="@string/settings_no_breastfeeding_desc"/>
|
android:text="@string/settings_dynamic_menu_desc"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
50
app/src/main/res/layout/activity_statistics.xml
Normal file
50
app/src/main/res/layout/activity_statistics.xml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
<?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:orientation="vertical"
|
||||||
|
android:padding="10dp">
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
|
<com.github.mikephil.charting.charts.HorizontalBarChart
|
||||||
|
android:id="@+id/bar_chart"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/no_data"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:gravity="center"
|
||||||
|
android:text="No Data"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="0"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/graph_type_selection"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/time_range_selection"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -8,10 +8,15 @@
|
|||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/dialog_puke_value"
|
android:id="@+id/dialog_amount_value"
|
||||||
android:layout_width="250dp"
|
android:layout_width="250dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingHorizontal="16dp"
|
android:paddingHorizontal="16dp"/>
|
||||||
android:paddingVertical="8dp"/>
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
35
app/src/main/res/layout/dialog_edit_bottle.xml
Normal file
35
app/src/main/res/layout/dialog_edit_bottle.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:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<NumberPicker
|
||||||
|
android:id="@+id/dialog_number_picker"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="ml"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
55
app/src/main/res/layout/dialog_edit_duration.xml
Normal file
55
app/src/main/res/layout/dialog_edit_duration.xml
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
<?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">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_duration"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:text="💤"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center"
|
||||||
|
android:layout_marginTop="20dp"
|
||||||
|
android:layout_marginHorizontal="10dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dialog_date_duration_minus5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="-5"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dialog_date_duration_now"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/now"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/dialog_date_duration_plus5"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="+5"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -24,4 +24,34 @@
|
|||||||
android:hint="@string/log_notes_dialog_note_hint"
|
android:hint="@string/log_notes_dialog_note_hint"
|
||||||
android:background="@drawable/textview_background"/>
|
android:background="@drawable/textview_background"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
android:gravity="end"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notes_template_prev"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:text="⬅️"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/notes_template_next"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="18dp"
|
||||||
|
android:text="➡️"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="15dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -4,16 +4,12 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="center"
|
android:layout_gravity="center"
|
||||||
android:gravity="center">
|
android:gravity="center"
|
||||||
|
android:orientation="vertical">
|
||||||
<NumberPicker
|
|
||||||
android:id="@+id/dialog_number_picker"
|
|
||||||
android:layout_width="150dp"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"/>
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:text="ml"/>
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
@@ -23,4 +23,11 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textSize="30sp"
|
android:textSize="30sp"
|
||||||
android:textColor="@color/accent"/>
|
android:textColor="@color/accent"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
38
app/src/main/res/layout/dialog_edit_weight.xml
Normal file
38
app/src/main/res/layout/dialog_edit_weight.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?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:orientation="vertical">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/dialog_number_edittext"
|
||||||
|
android:layout_width="150dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="number"
|
||||||
|
android:hint="0"
|
||||||
|
android:background="@drawable/textview_background"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="10dp"
|
||||||
|
android:text="g"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_date_picker"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginTop="20dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
@@ -36,9 +35,7 @@
|
|||||||
android:drawablePadding="10dp"
|
android:drawablePadding="10dp"
|
||||||
android:drawableTint="@color/accent"
|
android:drawableTint="@color/accent"
|
||||||
android:textSize="16sp"
|
android:textSize="16sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"/>
|
||||||
android:text="@string/dialog_event_detail_datetime_icon"
|
|
||||||
app:drawableEndCompat="@drawable/ic_edit" />
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/dialog_event_detail_type_quantity"
|
android:id="@+id/dialog_event_detail_type_quantity"
|
||||||
@@ -48,6 +45,18 @@
|
|||||||
android:textSize="28sp"
|
android:textSize="28sp"
|
||||||
android:text="@string/dialog_event_detail_quantity"/>
|
android:text="@string/dialog_event_detail_quantity"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/dialog_event_detail_type_date_end"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:layout_gravity="center_horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:drawablePadding="10dp"
|
||||||
|
android:drawableTint="@color/accent"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<ScrollView
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="200dp"
|
android:layout_height="200dp"
|
||||||
@@ -6,89 +6,21 @@
|
|||||||
android:background="@color/transparent">
|
android:background="@color/transparent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
android:id="@+id/layout_list"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/button_medicine"
|
android:id="@+id/button_statistics"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:padding="10dp"
|
android:padding="10dp"
|
||||||
android:background="@drawable/dropdown_list_item_background"
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
style="@style/OverflowMenuText"
|
style="@style/OverflowMenuText"
|
||||||
android:text="@string/overflow_event_medicine"/>
|
android:text="📊 Statistics"/>
|
||||||
|
|
||||||
<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"/>
|
|
||||||
|
|
||||||
|
<!-- Other buttons are inserted dynamically -->
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
11
app/src/main/res/layout/more_events_popup_item.xml
Normal file
11
app/src/main/res/layout/more_events_popup_item.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="@+id/tv"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_marginTop="10dp"
|
||||||
|
android:padding="10dp"
|
||||||
|
android:background="@drawable/dropdown_list_item_background"
|
||||||
|
style="@style/OverflowMenuText"
|
||||||
|
android:text="Item Template"/>
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?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">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/dialog_number_edittext"
|
|
||||||
android:layout_width="150dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:inputType="number"
|
|
||||||
android:hint="0"
|
|
||||||
android:background="@drawable/textview_background"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="10dp"
|
|
||||||
android:text="g"/>
|
|
||||||
</LinearLayout>
|
|
||||||
8
app/src/main/res/layout/statistics_spinner_item.xml
Normal file
8
app/src/main/res/layout/statistics_spinner_item.xml
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<TextView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:gravity="center"
|
||||||
|
android:padding="5dip" />
|
||||||
@@ -3,18 +3,9 @@
|
|||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="logbook">Ereignisprotokoll</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_bottle_desc">Fläschchen</string>
|
||||||
<string name="event_food_desc">Essen</string>
|
<string name="event_food_desc">Essen</string>
|
||||||
<string name="event_scale_desc">Gewicht</string>
|
<string name="event_weight_desc">Gewicht</string>
|
||||||
<string name="event_breastfeeding_left_desc">Stillen (links)</string>
|
<string name="event_breastfeeding_left_desc">Stillen (links)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Stillen</string>
|
<string name="event_breastfeeding_both_desc">Stillen</string>
|
||||||
<string name="event_breastfeeding_right_desc">Stillen (rechts)</string>
|
<string name="event_breastfeeding_right_desc">Stillen (rechts)</string>
|
||||||
@@ -27,13 +18,6 @@
|
|||||||
<string name="event_colic_desc">Blähungskolik</string>
|
<string name="event_colic_desc">Blähungskolik</string>
|
||||||
<string name="event_unknown_desc"></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_event_added">Ereignis gespeichert</string>
|
||||||
<string name="toast_logbook_saved">Logbuch 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_event_add_error">Ereignis konnte nicht protokolliert werden</string>
|
||||||
@@ -51,7 +35,6 @@
|
|||||||
<string name="no_connection_retry">Erneut versuchen</string>
|
<string name="no_connection_retry">Erneut versuchen</string>
|
||||||
|
|
||||||
<string name="settings_title">Einstellungen</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">Speicherort für Daten auswählen</string>
|
||||||
<string name="settings_storage_local">Auf dem Gerät</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_local_desc">Datenschutzfreundlichste Lösung: Deine Daten verlassen dein Gerät nicht</string>
|
||||||
@@ -77,10 +60,13 @@
|
|||||||
<string name="trim_logbook_dialog_button_ok">Jetzt bereinigen</string>
|
<string name="trim_logbook_dialog_button_ok">Jetzt bereinigen</string>
|
||||||
<string name="trim_logbook_dialog_button_cancel">Später erinnern</string>
|
<string name="trim_logbook_dialog_button_cancel">Später erinnern</string>
|
||||||
|
|
||||||
<string name="log_notes_dialog_description">Notizen:</string>
|
<string name="log_bottle_dialog_description">Trinkmenge eingeben</string>
|
||||||
<string name="log_medicine_dialog_description">Medikamentenname, Menge, Art, 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_description">Notizen:</string>
|
||||||
<string name="log_notes_dialog_note_hint">Notiz eingeben</string>
|
<string name="log_notes_dialog_note_hint">Notiz eingeben</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Menge (optional)</string>
|
||||||
|
<string name="log_temperature_dialog_description">Temperatur eingeben</string>
|
||||||
|
<string name="log_weight_dialog_description">Gewicht eingeben</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Ereignisdetails</string>
|
<string name="dialog_event_detail_title">Ereignisdetails</string>
|
||||||
<string name="dialog_event_detail_close_button">OK</string>
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
|||||||
@@ -3,18 +3,9 @@
|
|||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="logbook">Entrées enregistrées</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_bottle_desc">Biberon</string>
|
||||||
<string name="event_food_desc">Nourriture</string>
|
<string name="event_food_desc">Nourriture</string>
|
||||||
<string name="event_scale_desc">Poids</string>
|
<string name="event_weight_desc">Poids</string>
|
||||||
<string name="event_breastfeeding_left_desc">Allaitement (sein gauche)</string>
|
<string name="event_breastfeeding_left_desc">Allaitement (sein gauche)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Allaitement</string>
|
<string name="event_breastfeeding_both_desc">Allaitement</string>
|
||||||
<string name="event_breastfeeding_right_desc">Allaitement (sein droit)</string>
|
<string name="event_breastfeeding_right_desc">Allaitement (sein droit)</string>
|
||||||
@@ -27,13 +18,6 @@
|
|||||||
<string name="event_colic_desc">Colique gazeuse</string>
|
<string name="event_colic_desc">Colique gazeuse</string>
|
||||||
<string name="event_unknown_desc"></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_event_added">Entrée ajoutée</string>
|
||||||
<string name="toast_logbook_saved">Journal ajouté</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_event_add_error">Impossible d\'enregistrer cette entrée</string>
|
||||||
@@ -76,10 +60,13 @@
|
|||||||
<string name="trim_logbook_dialog_button_ok">Supprimer les vieilles entrées maintenant</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="trim_logbook_dialog_button_cancel">Me rappeller plus tard</string>
|
||||||
|
|
||||||
<string name="log_notes_dialog_description">Notes:</string>
|
<string name="log_bottle_dialog_description">Renseignez la quantité contenue dans le biberon</string>
|
||||||
<string name="log_medicine_dialog_description">nom du médicament, quantité, type, 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_description">Notes:</string>
|
||||||
<string name="log_notes_dialog_note_hint">Notes ...</string>
|
<string name="log_notes_dialog_note_hint">Notes ...</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Quantité (ou vide)</string>
|
||||||
|
<string name="log_temperature_dialog_description">Renseignez la Température</string>
|
||||||
|
<string name="log_weight_dialog_description">Renseignez le poids</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Détails de l\'entrée</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_close_button">OK</string>
|
||||||
|
|||||||
@@ -3,25 +3,9 @@
|
|||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="logbook">Diario di bordo</string>
|
<string name="logbook">Diario di bordo</string>
|
||||||
|
|
||||||
<string name="log_bottle_dialog_title">Biberon</string>
|
|
||||||
<string name="log_bottle_dialog_description">Inserisci la quantità contenuta nel biberon</string>
|
|
||||||
|
|
||||||
<string name="log_weight_dialog_title">Pesata</string>
|
|
||||||
<string name="log_weight_dialog_description">Inserisci il peso rilevato</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_bottle_desc">Biberon</string>
|
||||||
<string name="event_food_desc">Cibo</string>
|
<string name="event_food_desc">Cibo</string>
|
||||||
<string name="event_scale_desc">Pesata</string>
|
<string name="event_weight_desc">Pesata</string>
|
||||||
<string name="event_breastfeeding_left_desc">Allatt. al seno (sx)</string>
|
<string name="event_breastfeeding_left_desc">Allatt. al seno (sx)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Allatt. al seno</string>
|
<string name="event_breastfeeding_both_desc">Allatt. al seno</string>
|
||||||
<string name="event_breastfeeding_right_desc">Allatt. al seno (dx)</string>
|
<string name="event_breastfeeding_right_desc">Allatt. al seno (dx)</string>
|
||||||
@@ -76,10 +60,13 @@
|
|||||||
<string name="trim_logbook_dialog_button_ok">Cancella i più vecchi</string>
|
<string name="trim_logbook_dialog_button_ok">Cancella i più vecchi</string>
|
||||||
<string name="trim_logbook_dialog_button_cancel">Ricordamelo dopo</string>
|
<string name="trim_logbook_dialog_button_cancel">Ricordamelo dopo</string>
|
||||||
|
|
||||||
<string name="log_notes_dialog_description">Note:</string>
|
<string name="log_bottle_dialog_description">Inserisci la quantità contenuta nel biberon</string>
|
||||||
<string name="log_medicine_dialog_description">Nome della medicina, quantità, formato, 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_description">Note:</string>
|
||||||
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
|
<string name="log_notes_dialog_note_hint">Inserisci le note</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Quantità, o vuoto</string>
|
||||||
|
<string name="log_temperature_dialog_description">Inserisci la temperatura</string>
|
||||||
|
<string name="log_weight_dialog_description">Inserisci il peso rilevato</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Dettaglio evento</string>
|
<string name="dialog_event_detail_title">Dettaglio evento</string>
|
||||||
<string name="dialog_event_detail_close_button">OK</string>
|
<string name="dialog_event_detail_close_button">OK</string>
|
||||||
|
|||||||
@@ -1,8 +1,39 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string-array name="AmountLabels">
|
<string-array name="AmountLabels">
|
||||||
|
<item>@string/amount_unspecified</item>
|
||||||
<item>@string/amount_little</item>
|
<item>@string/amount_little</item>
|
||||||
<item>@string/amount_normal</item>
|
<item>@string/amount_normal</item>
|
||||||
<item>@string/amount_plenty</item>
|
<item>@string/amount_plenty</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="StatisticsTypeLabels">
|
||||||
|
<item>@string/statistics_bottle_sum</item>
|
||||||
|
<item>@string/statistics_bottle_events</item>
|
||||||
|
<item>@string/statistics_sleep_sum</item>
|
||||||
|
<item>@string/statistics_sleep_events</item>
|
||||||
|
<item>@string/statistics_sleep_pattern</item>
|
||||||
|
<item>@string/statistics_medicine_events</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="StatisticsTypeValues">
|
||||||
|
<item>BOTTLE_SUM</item>
|
||||||
|
<item>BOTTLE_EVENTS</item>
|
||||||
|
<item>SLEEP_SUM</item>
|
||||||
|
<item>SLEEP_EVENTS</item>
|
||||||
|
<item>SLEEP_PATTERN</item>
|
||||||
|
<item>MEDICINE_EVENTS</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="StatisticsTimeLabels">
|
||||||
|
<item>Day</item>
|
||||||
|
<item>Week</item>
|
||||||
|
<item>Month</item>
|
||||||
|
</string-array>
|
||||||
|
|
||||||
|
<string-array name="StatisticsTimeValues">
|
||||||
|
<item>DAY</item>
|
||||||
|
<item>WEEK</item>
|
||||||
|
<item>MONTH</item>
|
||||||
|
</string-array>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -3,24 +3,13 @@
|
|||||||
<string name="title">🌜 LunaTracker 🌛</string>
|
<string name="title">🌜 LunaTracker 🌛</string>
|
||||||
<string name="logbook">Logged events</string>
|
<string name="logbook">Logged events</string>
|
||||||
|
|
||||||
<string name="log_bottle_dialog_title">Baby bottle</string>
|
<!-- menu header items -->
|
||||||
<string name="log_bottle_dialog_description">Insert the quantity contained in the baby bottle</string>
|
|
||||||
|
|
||||||
<string name="log_weight_dialog_title">Weight</string>
|
|
||||||
<string name="log_weight_dialog_description">Insert the weight</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_bottle_type" translatable="false">🍼</string>
|
||||||
<string name="event_food_type" translatable="false">🥣</string>
|
<string name="event_food_type" translatable="false">🥣</string>
|
||||||
<string name="event_scale_type" translatable="false">⚖️</string>
|
<string name="event_weight_type" translatable="false">⚖️</string>
|
||||||
<string name="event_breastfeeding_left_type" translatable="false">🤱 ←</string>
|
<string name="event_breastfeeding_left_type" translatable="false">🤱⬅️</string>
|
||||||
<string name="event_breastfeeding_both_type" translatable="false">🤱 ↔</string>
|
<string name="event_breastfeeding_both_type" translatable="false">🤱↔️</string>
|
||||||
<string name="event_breastfeeding_right_type" translatable="false">🤱 →</string>
|
<string name="event_breastfeeding_right_type" translatable="false">🤱➡️️</string>
|
||||||
<string name="event_diaperchange_poo_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_diaperchange_pee_type" translatable="false">🚼 💧</string>
|
||||||
<string name="event_medicine_type" translatable="false">💊</string>
|
<string name="event_medicine_type" translatable="false">💊</string>
|
||||||
@@ -30,16 +19,37 @@
|
|||||||
<string name="event_colic_type" translatable="false">💨</string>
|
<string name="event_colic_type" translatable="false">💨</string>
|
||||||
<string name="event_puke_type" translatable="false">🤮</string>
|
<string name="event_puke_type" translatable="false">🤮</string>
|
||||||
<string name="event_bath_type" translatable="false">🛁</string>
|
<string name="event_bath_type" translatable="false">🛁</string>
|
||||||
<string name="event_unknown_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>
|
<!-- dropdown menu item -->
|
||||||
|
<string name="event_type_item_bottle">🍼 Bottle</string>
|
||||||
|
<string name="event_type_item_food">🥣 Food</string>
|
||||||
|
<string name="event_type_item_weight">⚖️ Weight</string>
|
||||||
|
<string name="event_type_item_breastfeeding_left">🤱⬅️ Nursing</string>
|
||||||
|
<string name="event_type_item_breastfeeding_both">🤱↔️ Nursing</string>
|
||||||
|
<string name="event_type_item_breastfeeding_right">🤱➡️️ Nursing</string>
|
||||||
|
<string name="event_type_item_diaperchange_poo">🚼💩 Diaper</string>
|
||||||
|
<string name="event_type_item_diaperchange_pee">🚼💧 Diaper</string>
|
||||||
|
<string name="event_type_item_medicine">💊 Medicine</string>
|
||||||
|
<string name="event_type_item_enema">🪠 Enema</string>
|
||||||
|
<string name="event_type_item_note">📝 Note</string>
|
||||||
|
<string name="event_type_item_temperature">🌡️ Temperature</string>
|
||||||
|
<string name="event_type_item_colic">💨 Colic</string>
|
||||||
|
<string name="event_type_item_puke">🤮 Puke</string>
|
||||||
|
<string name="event_type_item_sleep">💤 Sleep</string>
|
||||||
|
<string name="event_type_item_bath">🛁 Bath</string>
|
||||||
|
<string name="event_type_item_unknown">❓ Unknown</string>
|
||||||
|
|
||||||
|
<!-- dialog titles -->
|
||||||
|
<string name="event_bottle_desc">Milk Bottle</string>
|
||||||
<string name="event_food_desc">Food</string>
|
<string name="event_food_desc">Food</string>
|
||||||
<string name="event_scale_desc">Weight</string>
|
<string name="event_weight_desc">Weight</string>
|
||||||
<string name="event_breastfeeding_left_desc">Breastfeeding (left)</string>
|
<string name="event_breastfeeding_left_desc">Nursing (left)</string>
|
||||||
<string name="event_breastfeeding_both_desc">Breastfeeding</string>
|
<string name="event_breastfeeding_both_desc">Nursing (both)</string>
|
||||||
<string name="event_breastfeeding_right_desc">Breastfeeding (right)</string>
|
<string name="event_breastfeeding_right_desc">Nursing (right)</string>
|
||||||
<string name="event_diaperchange_poo_desc">Diaper chg (poo)</string>
|
<string name="event_diaperchange_poo_desc">Diaper Change (poo)</string>
|
||||||
<string name="event_diaperchange_pee_desc">Diaper chg (pee)</string>
|
<string name="event_diaperchange_pee_desc">Diaper Change (pee)</string>
|
||||||
<string name="event_medicine_desc">Medicine</string>
|
<string name="event_medicine_desc">Medicine</string>
|
||||||
<string name="event_enema_desc">Enema</string>
|
<string name="event_enema_desc">Enema</string>
|
||||||
<string name="event_note_desc">Note</string>
|
<string name="event_note_desc">Note</string>
|
||||||
@@ -47,21 +57,14 @@
|
|||||||
<string name="event_colic_desc">Gaseous colic</string>
|
<string name="event_colic_desc">Gaseous colic</string>
|
||||||
<string name="event_puke_desc">Puke</string>
|
<string name="event_puke_desc">Puke</string>
|
||||||
<string name="event_bath_desc">Bath</string>
|
<string name="event_bath_desc">Bath</string>
|
||||||
<string name="event_unknown_desc"></string>
|
<string name="event_sleep_desc">Sleep</string>
|
||||||
|
<string name="event_unknown_desc">Unknown</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_event_added">Event logged</string>
|
||||||
<string name="toast_logbook_saved">Logbook saved</string>
|
<string name="toast_logbook_saved">Logbook saved</string>
|
||||||
<string name="toast_event_add_error">Unable to log the event</string>
|
<string name="toast_event_add_error">Unable to log the event</string>
|
||||||
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
|
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
|
||||||
|
<string name="toast_date_error">Invalid date.</string>
|
||||||
|
|
||||||
<string name="now">now</string>
|
<string name="now">now</string>
|
||||||
<string name="second_ago">sec</string>
|
<string name="second_ago">sec</string>
|
||||||
@@ -75,6 +78,7 @@
|
|||||||
<string name="year_ago">year</string>
|
<string name="year_ago">year</string>
|
||||||
<string name="years_ago">years</string>
|
<string name="years_ago">years</string>
|
||||||
|
|
||||||
|
<string name="amount_unspecified"></string>
|
||||||
<string name="amount_little">Little</string>
|
<string name="amount_little">Little</string>
|
||||||
<string name="amount_normal">Normal</string>
|
<string name="amount_normal">Normal</string>
|
||||||
<string name="amount_plenty">Plenty</string>
|
<string name="amount_plenty">Plenty</string>
|
||||||
@@ -84,6 +88,10 @@
|
|||||||
<string name="no_connection_go_to_settings">Settings</string>
|
<string name="no_connection_go_to_settings">Settings</string>
|
||||||
<string name="no_connection_retry">Retry</string>
|
<string name="no_connection_retry">Retry</string>
|
||||||
|
|
||||||
|
<string name="statistics_title">Statistics</string>
|
||||||
|
|
||||||
|
<string name="settings_dynamic_menu">Dynamic Menu</string>
|
||||||
|
<string name="settings_dynamic_menu_desc">Populate the header menu with the most used events.</string>
|
||||||
<string name="settings_title">Settings</string>
|
<string name="settings_title">Settings</string>
|
||||||
<string name="settings_signature">Signature</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_signature_desc">Attach a signature to each event you create and for others to see. Useful if multiple people add events.</string>
|
||||||
@@ -102,8 +110,6 @@
|
|||||||
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
|
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
|
||||||
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
|
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
|
||||||
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
|
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
|
||||||
<string name="settings_no_breastfeeding">No Breastfeeding</string>
|
|
||||||
<string name="settings_no_breastfeeding_desc">Hide the Breastfeeding buttons for when they are not needed.</string>
|
|
||||||
<string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string>
|
<string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string>
|
||||||
<string name="settings_generic_error">Error: </string>
|
<string name="settings_generic_error">Error: </string>
|
||||||
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
|
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
|
||||||
@@ -114,10 +120,16 @@
|
|||||||
<string name="trim_logbook_dialog_button_ok">Trim it now</string>
|
<string name="trim_logbook_dialog_button_ok">Trim it now</string>
|
||||||
<string name="trim_logbook_dialog_button_cancel">Remind me later</string>
|
<string name="trim_logbook_dialog_button_cancel">Remind me later</string>
|
||||||
|
|
||||||
<string name="log_notes_dialog_description">Notes:</string>
|
<string name="log_amount_dialog_description">Select the amount:</string>
|
||||||
|
<string name="log_bottle_dialog_description">Insert the quantity contained in the baby bottle:</string>
|
||||||
<string name="log_medicine_dialog_description">Medicine name, quantity, type, 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_description">Notes:</string>
|
||||||
<string name="log_notes_dialog_note_hint">Write some notes</string>
|
<string name="log_notes_dialog_note_hint">Write some notes</string>
|
||||||
|
<string name="log_notes_dialog_qty_hint">Quantity (or empty)</string>
|
||||||
|
<string name="log_temperature_dialog_description">Select the temperature:</string>
|
||||||
|
<string name="log_unknown_dialog_description"></string>
|
||||||
|
<string name="log_weight_dialog_description">Insert the weight:</string>
|
||||||
|
<string name="log_sleep_dialog_description">Set sleep duration:</string>
|
||||||
|
|
||||||
<string name="measurement_unit_liquid_base_metric" translatable="false">ml</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_base_metric" translatable="false">g</string>
|
||||||
@@ -128,13 +140,20 @@
|
|||||||
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
|
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
|
||||||
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
|
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
|
||||||
|
|
||||||
|
<string name="statistics_bottle_events">Bottle Events</string>
|
||||||
|
<string name="statistics_bottle_sum">Bottle Per Day</string>
|
||||||
|
<string name="statistics_medicine_events">Medicine Events</string>
|
||||||
|
<string name="statistics_sleep_sum">Sleep Per Day</string>
|
||||||
|
<string name="statistics_sleep_events">Sleep Events</string>
|
||||||
|
<string name="statistics_sleep_pattern">Sleep Pattern</string>
|
||||||
|
|
||||||
<string name="row_luna_event_description">Description</string>
|
<string name="row_luna_event_description">Description</string>
|
||||||
<string name="row_luna_event_quantity">Qty</string>
|
<string name="row_luna_event_quantity">Qty</string>
|
||||||
<string name="row_luna_event_time">Time</string>
|
<string name="row_luna_event_time">Time</string>
|
||||||
|
|
||||||
<string name="dialog_event_detail_title">Event detail</string>
|
<string name="dialog_event_detail_title">Event detail</string>
|
||||||
<string name="dialog_event_detail_datetime_icon" translatable="false">🕒 %s</string>
|
<string name="dialog_event_detail_close_button">Close</string>
|
||||||
<string name="dialog_event_detail_close_button">OK</string>
|
<string name="dialog_event_detail_save_button">Save</string>
|
||||||
<string name="dialog_event_detail_delete_button">Delete</string>
|
<string name="dialog_event_detail_delete_button">Delete</string>
|
||||||
<string name="dialog_event_detail_quantity">Quantity</string>
|
<string name="dialog_event_detail_quantity">Quantity</string>
|
||||||
<string name="dialog_event_detail_notes">Notes</string>
|
<string name="dialog_event_detail_notes">Notes</string>
|
||||||
|
|||||||
@@ -4,6 +4,11 @@
|
|||||||
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
|
<style name="Theme.LunaTracker" parent="Theme.AppCompat.NoActionBar">
|
||||||
<item name="colorAccent">@color/accent</item>
|
<item name="colorAccent">@color/accent</item>
|
||||||
<item name="android:textColor">@color/textColor</item>
|
<item name="android:textColor">@color/textColor</item>
|
||||||
|
|
||||||
|
<!-- make the screen not overlap with the system bars -->
|
||||||
|
<item name="android:fitsSystemWindows">true</item>
|
||||||
|
<item name="android:windowTranslucentStatus">true</item>
|
||||||
|
<item name="android:windowTranslucentNavigation">true</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="OverflowMenuText">
|
<style name="OverflowMenuText">
|
||||||
|
|||||||
@@ -9,8 +9,11 @@ lifecycleRuntimeKtx = "2.6.1"
|
|||||||
activityCompose = "1.8.0"
|
activityCompose = "1.8.0"
|
||||||
composeBom = "2024.04.01"
|
composeBom = "2024.04.01"
|
||||||
appcompat = "1.7.0"
|
appcompat = "1.7.0"
|
||||||
|
mpandroidchart = "v4.2.2"
|
||||||
|
mpandroidchartVersion = "v3.1.0"
|
||||||
recyclerview = "1.3.2"
|
recyclerview = "1.3.2"
|
||||||
material = "1.12.0"
|
material = "1.12.0"
|
||||||
|
sardineAndroid = "v0.9"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
@@ -30,6 +33,9 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
|
|||||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
|
mpandroidchart = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchart" }
|
||||||
|
mpandroidchart-vv310 = { module = "com.github.PhilJay:MPAndroidChart", version.ref = "mpandroidchartVersion" }
|
||||||
|
sardine-android = { module = "com.github.thegrizzlylabs:sardine-android", version.ref = "sardineAndroid" }
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ dependencyResolutionManagement {
|
|||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven(url = "https://jitpack.io")
|
maven(url = uri("https://jitpack.io"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user