6 Commits

Author SHA1 Message Date
e566624a29 remove log event title
Keep the main page simpler and have a bit more
space for log messages.
2025-09-16 09:39:58 +02:00
77f6a89fcc add setting to disable breastfeeding buttons
Some women do not breastfeed. Hide the buttons
in order to have more space for log messages.
2025-09-16 09:39:58 +02:00
9efe2a3574 replace dp with sp
As suggested by android-studio.
2025-09-16 09:39:58 +02:00
3a65d09e58 values: escape apostrophe characters 2025-09-16 09:39:58 +02:00
1508db2a56 add password toggle view icon 2025-09-16 09:39:58 +02:00
674d4fd744 layout_marginLeft is deprecated 2025-09-16 09:39:21 +02:00
29 changed files with 180 additions and 574 deletions

1
.idea/.name generated
View File

@@ -1 +0,0 @@
LunaTracker

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="AndroidProjectSystem">
<option name="providerId" value="com.android.tools.idea.GradleProjectSystem" />
</component>
</project>

6
.idea/compiler.xml generated
View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="21" />
</component>
</project>

View File

@@ -1,7 +1,5 @@
# 🌜 LunaTracker 🌛
[<img src="https://fdroid.gitlab.io/artwork/badge/get-it-on.png" alt="Get it on F-Droid" height="80">](https://f-droid.org/it/packages/it.danieleverducci.lunatracker/)
LunaTracker is a newborn baby tracking app.
Parenting can be tough. You get home from the hospital, exhausted, with this little fragile unknown thingy that has no user manual and a single way to let you know something's not ok: crying.
@@ -13,8 +11,7 @@ Dedicated to my daughter Luna.
![Screenshot](fastlane/metadata/android/en-US/images/phoneScreenshots/1.png)
## Thanks for the valuable contributions to:
Thanks for the valuable contributions to:
- Chepycou (French translation)
- Daniel Neubauer (German translation)
- Moritz Warning (Various bugfixes and new features)
Chepycou (French translation)
Daniel Neubauer (German translation)

View File

@@ -12,8 +12,8 @@ android {
applicationId = "it.danieleverducci.lunatracker"
minSdk = 21
targetSdk = 34
versionCode = 5
versionName = "0.7"
versionCode = 4
versionName = "0.6"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

View File

@@ -39,16 +39,16 @@ import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
import kotlinx.coroutines.Runnable
import okio.IOException
import org.json.JSONException
import utils.DateUtils
import utils.NumericUtils
import java.text.DateFormat
import java.util.Calendar
import java.util.Date
class MainActivity : AppCompatActivity() {
companion object {
const val TAG = "MainActivity"
const val UPDATE_EVERY_SECS: Long = 30
const val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
val TAG = "MainActivity"
val UPDATE_EVERY_SECS: Long = 30
val DEBUG_CHECK_LOGBOOK_CONSISTENCY = false
}
var logbook: Logbook? = null
@@ -57,7 +57,6 @@ class MainActivity : AppCompatActivity() {
lateinit var buttonsContainer: ViewGroup
lateinit var recyclerView: RecyclerView
lateinit var handler: Handler
var signature = ""
var savingEvent = false
val updateListRunnable: Runnable = Runnable {
if (logbook != null && !pauseLogbookUpdate)
@@ -75,9 +74,9 @@ class MainActivity : AppCompatActivity() {
// Show view
setContentView(R.layout.activity_main)
progressIndicator = findViewById(R.id.progress_indicator)
buttonsContainer = findViewById(R.id.buttons_container)
recyclerView = findViewById(R.id.list_events)
progressIndicator = findViewById<LinearProgressIndicator>(R.id.progress_indicator)
buttonsContainer = findViewById<ViewGroup>(R.id.buttons_container)
recyclerView = findViewById<RecyclerView>(R.id.list_events)
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
// Set listeners
@@ -113,26 +112,26 @@ class MainActivity : AppCompatActivity() {
moreButton.setOnClickListener {
showOverflowPopupWindow(moreButton)
}
findViewById<View>(R.id.button_no_connection_settings).setOnClickListener {
findViewById<View>(R.id.button_no_connection_settings).setOnClickListener({
showSettings()
}
findViewById<View>(R.id.button_settings).setOnClickListener {
})
findViewById<View>(R.id.button_settings).setOnClickListener({
showSettings()
}
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener {
})
findViewById<View>(R.id.button_no_connection_retry).setOnClickListener({
// This may happen at start, when logbook is still null: better ask the logbook list
loadLogbookList()
}
findViewById<View>(R.id.button_sync).setOnClickListener {
})
findViewById<View>(R.id.button_sync).setOnClickListener({
loadLogbookList()
}
})
}
private fun setListAdapter(items: ArrayList<LunaEvent>) {
val adapter = LunaEventRecyclerAdapter(this, items)
adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener {
adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener{
override fun onItemClick(event: LunaEvent) {
showEventDetailDialog(event, items)
showEventDetailDialog(event)
}
}
recyclerView.adapter = adapter
@@ -169,8 +168,6 @@ class MainActivity : AppCompatActivity() {
logbookRepo = FileLogbookRepository()
}
signature = settingsRepository.loadSignature()
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) {
true -> View.GONE
@@ -262,26 +259,6 @@ class MainActivity : AppCompatActivity() {
alertDialog.show()
}
fun askPukeValue() {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.puke_dialog, null)
d.setTitle(R.string.log_puke_dialog_title)
d.setMessage(R.string.log_puke_dialog_description)
d.setView(dialogView)
val spinner = dialogView.findViewById<Spinner>(R.id.dialog_puke_value)
spinner.adapter = ArrayAdapter.createFromResource(this, R.array.AmountLabels, android.R.layout.simple_spinner_dropdown_item)
spinner.setSelection(1)
d.setPositiveButton(android.R.string.ok) { dialogInterface, i ->
val pos = spinner.selectedItemPosition
logEvent(LunaEvent(LunaEvent.TYPE_PUKE, pos))
}
d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() }
val alertDialog = d.create()
alertDialog.show()
}
fun askNotes(lunaEvent: LunaEvent) {
val d = AlertDialog.Builder(this)
val dialogView = layoutInflater.inflate(R.layout.dialog_notes, null)
@@ -331,52 +308,25 @@ class MainActivity : AppCompatActivity() {
alertDialog.show()
}
fun getPreviousSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
var previousEvent: LunaEvent? = null
for (item in items) {
if (item.type == event.type && item.time < event.time) {
if (previousEvent == null) {
previousEvent = item
} else if (previousEvent.time < item.time) {
previousEvent = item
}
}
}
return previousEvent
}
fun getNextSameEvent(event: LunaEvent, items: ArrayList<LunaEvent>): LunaEvent? {
var nextEvent: LunaEvent? = null
for (item in items) {
if (item.type == event.type && item.time > event.time) {
if (nextEvent == null) {
nextEvent = item
} else if (nextEvent.time > item.time) {
nextEvent = item
}
}
}
return nextEvent
}
fun showEventDetailDialog(event: LunaEvent, items: ArrayList<LunaEvent>) {
fun showEventDetailDialog(event: LunaEvent) {
// Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
pauseLogbookUpdate = true
val dateFormat = DateFormat.getDateTimeInstance();
val d = AlertDialog.Builder(this)
d.setTitle(R.string.dialog_event_detail_title)
val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji).text = event.getTypeEmoji(this)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description).text = event.getTypeDescription(this)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity).text =
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_emoji).setText(event.getTypeEmoji(this))
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_description).setText(event.getTypeDescription(this))
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_quantity).setText(
NumericUtils(this).formatEventQuantity(event)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).text = event.notes
)
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_notes).setText(event.notes)
val currentDateTime = Calendar.getInstance()
currentDateTime.time = Date(event.time * 1000)
val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
dateTextView.setOnClickListener {
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time))
dateTextView.setOnClickListener({
// Show datetime picker
val startYear = currentDateTime.get(Calendar.YEAR)
val startMonth = currentDateTime.get(Calendar.MONTH)
@@ -384,19 +334,21 @@ class MainActivity : AppCompatActivity() {
val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY)
val startMinute = currentDateTime.get(Calendar.MINUTE)
DatePickerDialog(this, { _, year, month, day ->
TimePickerDialog(this, { _, hour, minute ->
DatePickerDialog(this, DatePickerDialog.OnDateSetListener { _, year, month, day ->
TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, hour, minute ->
val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day, hour, minute)
currentDateTime.time = pickedDateTime.time
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time))
// Save event and move it to the right position in the logbook
event.time = pickedDateTime.time.time / 1000 // Seconds since epoch
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
event.time = currentDateTime.time.time / 1000 // Seconds since epoch
logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged()
saveLogbook()
}, startHour, startMinute, android.text.format.DateFormat.is24HourFormat(this@MainActivity)).show()
}, startHour, startMinute, false).show()
}, startYear, startMonth, startDay).show()
}
})
d.setView(dialogView)
d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i -> dialogInterface.dismiss() }
@@ -408,44 +360,6 @@ class MainActivity : AppCompatActivity() {
// Resume logbook update
pauseLogbookUpdate = false
})
// show optional signature
if (event.signature.isNotEmpty()) {
val signatureTextEdit = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature)
signatureTextEdit.text = String.format(getString(R.string.dialog_event_detail_signature), event.signature)
signatureTextEdit.visibility = View.VISIBLE
}
// create next/previous links to events of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
val nextTextView = dialogView.findViewById<TextView>(R.id.dialog_event_next)
val nextEvent = getNextSameEvent(event, items)
val previousEvent = getPreviousSameEvent(event, items)
if (previousEvent != null) {
val emoji = previousEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, event.time - previousEvent.time)
previousTextView.text = String.format("⬅️ %s %s", emoji, time)
previousTextView.setOnClickListener {
alertDialog.cancel()
showEventDetailDialog(previousEvent, items)
}
} else {
previousTextView.visibility = View.GONE
}
if (nextEvent != null) {
val emoji = nextEvent.getTypeEmoji(applicationContext)
val time = DateUtils.formatTimeDuration(applicationContext, nextEvent.time - event.time)
nextTextView.text = String.format("%s %s ➡️", time, emoji)
nextTextView.setOnClickListener {
alertDialog.cancel()
showEventDetailDialog(nextEvent, items)
}
} else {
nextTextView.visibility = View.GONE
}
}
fun showAddLogbookDialog(requestedByUser: Boolean) {
@@ -484,7 +398,7 @@ class MainActivity : AppCompatActivity() {
sAdapter.setDropDownViewResource(R.layout.row_logbook_spinner)
for (ln in logbooksNames) {
sAdapter.add(
ln.ifEmpty { getString(R.string.default_logbook_name) }
if (ln.isEmpty()) getString(R.string.default_logbook_name) else ln
)
}
spinner.adapter = sAdapter
@@ -502,6 +416,7 @@ class MainActivity : AppCompatActivity() {
}
override fun onNothingSelected(parent: AdapterView<*>?) {}
}
})
}
@@ -651,6 +566,7 @@ class MainActivity : AppCompatActivity() {
onRepoError(getString(R.string.settings_generic_error) + error.toString())
})
}
})
}
@@ -665,8 +581,6 @@ class MainActivity : AppCompatActivity() {
fun logEvent(event: LunaEvent) {
savingEvent(true)
event.signature = signature
setLoading(true)
logbook?.logs?.add(0, event)
recyclerView.adapter?.notifyItemInserted(0)
@@ -800,10 +714,10 @@ class MainActivity : AppCompatActivity() {
isOutsideTouchable = true
val inflater = LayoutInflater.from(anchor.context)
contentView = inflater.inflate(R.layout.more_events_popup, null)
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener {
contentView.findViewById<View>(R.id.button_medicine).setOnClickListener({
askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE))
dismiss()
}
})
contentView.findViewById<View>(R.id.button_enema).setOnClickListener({
logEvent(LunaEvent(LunaEvent.TYPE_ENEMA))
dismiss()
@@ -816,10 +730,6 @@ class MainActivity : AppCompatActivity() {
askTemperatureValue()
dismiss()
})
contentView.findViewById<View>(R.id.button_puke).setOnClickListener({
askPukeValue()
dismiss()
})
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({
logEvent(
LunaEvent(LunaEvent.TYPE_COLIC)
@@ -830,12 +740,6 @@ class MainActivity : AppCompatActivity() {
askWeightValue()
dismiss()
})
contentView.findViewById<View>(R.id.button_bath).setOnClickListener({
logEvent(
LunaEvent(LunaEvent.TYPE_BATH)
)
dismiss()
})
}.also { popupWindow ->
popupWindow.setOnDismissListener({
Handler(mainLooper).postDelayed({

View File

@@ -2,7 +2,6 @@ package it.danieleverducci.lunatracker
import android.os.Bundle
import android.view.View
import android.widget.EditText
import android.widget.RadioButton
import android.widget.TextView
import android.widget.Toast
@@ -13,8 +12,10 @@ import com.thegrizzlylabs.sardineandroid.impl.SardineException
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener
import it.danieleverducci.lunatracker.repository.LogbookRepository
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
import okio.IOException
import org.json.JSONException
open class SettingsActivity : AppCompatActivity() {
protected lateinit var settingsRepository: LocalSettingsRepository
@@ -25,7 +26,6 @@ open class SettingsActivity : AppCompatActivity() {
protected lateinit var textViewWebDAVPass: TextView
protected lateinit var progressIndicator: LinearProgressIndicator
protected lateinit var switchNoBreastfeeding: SwitchMaterial
protected lateinit var textViewSignature: EditText
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -38,7 +38,6 @@ open class SettingsActivity : AppCompatActivity() {
textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass)
progressIndicator = findViewById(R.id.progress_indicator)
switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
textViewSignature = findViewById(R.id.settings_signature)
findViewById<View>(R.id.settings_save).setOnClickListener({
validateAndSave()
@@ -55,20 +54,18 @@ open class SettingsActivity : AppCompatActivity() {
val dataRepo = settingsRepository.loadDataRepository()
val webDavCredentials = settingsRepository.loadWebdavCredentials()
val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
val signature = settingsRepository.loadSignature()
when (dataRepo) {
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
}
textViewSignature.setText(signature)
switchNoBreastfeeding.isChecked = noBreastfeeding
if (webDavCredentials != null) {
textViewWebDAVUrl.text = webDavCredentials[0]
textViewWebDAVUser.text = webDavCredentials[1]
textViewWebDAVPass.text = webDavCredentials[2]
textViewWebDAVUrl.setText(webDavCredentials[0])
textViewWebDAVUser.setText(webDavCredentials[1])
textViewWebDAVPass.setText(webDavCredentials[2])
}
}
@@ -161,7 +158,6 @@ open class SettingsActivity : AppCompatActivity() {
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
)
settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
settingsRepository.saveSignature(textViewSignature.text.toString())
settingsRepository.saveWebdavCredentials(
textViewWebDAVUrl.text.toString(),
textViewWebDAVUser.text.toString(),
@@ -176,7 +172,7 @@ open class SettingsActivity : AppCompatActivity() {
*/
private fun copyLocalLogbooksToWebdav(webDAVLogbookRepository: WebDAVLogbookRepository, listener: OnCopyLocalLogbooksToWebdavFinishedListener) {
Thread(Runnable {
val errors = StringBuilder()
var errors = StringBuilder()
val fileLogbookRepo = FileLogbookRepository()
val logbooks = fileLogbookRepo.getAllLogbooks(this)
for (logbook in logbooks) {

View File

@@ -53,7 +53,7 @@ class LunaEventRecyclerAdapter: RecyclerView.Adapter<LunaEventRecyclerAdapter.Lu
holder.quantity.setTextColor(ContextCompat.getColor(context, R.color.textColor))
// Contents
holder.type.text = item.getTypeEmoji(context)
holder.description.text = when (item.type) {
holder.description.text = when(item.type) {
LunaEvent.TYPE_MEDICINE -> item.notes
LunaEvent.TYPE_NOTE -> item.notes
LunaEvent.TYPE_CUSTOM -> item.notes

View File

@@ -2,7 +2,7 @@ package it.danieleverducci.lunatracker.entities
class Logbook(val name: String) {
companion object {
const val MAX_SAFE_LOGBOOK_SIZE = 30000
val MAX_SAFE_LOGBOOK_SIZE = 30000
}
val logs = ArrayList<LunaEvent>()

View File

@@ -14,22 +14,20 @@ import java.util.Date
class LunaEvent: Comparable<LunaEvent> {
companion object {
const val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
const val TYPE_WEIGHT = "WEIGHT"
const val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
const val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
const val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
const val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
const val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
const val TYPE_MEDICINE = "MEDICINE"
const val TYPE_ENEMA = "ENEMA"
const val TYPE_NOTE = "NOTE"
const val TYPE_CUSTOM = "CUSTOM"
const val TYPE_COLIC = "COLIC"
const val TYPE_TEMPERATURE = "TEMPERATURE"
const val TYPE_FOOD = "FOOD"
const val TYPE_PUKE = "PUKE"
const val TYPE_BATH = "BATH"
val TYPE_BABY_BOTTLE = "BABY_BOTTLE"
val TYPE_WEIGHT = "WEIGHT"
val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE"
val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE"
val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE"
val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO"
val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE"
val TYPE_MEDICINE = "MEDICINE"
val TYPE_ENEMA = "ENEMA"
val TYPE_NOTE = "NOTE"
val TYPE_CUSTOM = "CUSTOM"
val TYPE_COLIC = "COLIC"
val TYPE_TEMPERATURE = "TEMPERATURE"
val TYPE_FOOD = "FOOD"
}
private val jo: JSONObject
@@ -55,12 +53,6 @@ class LunaEvent: Comparable<LunaEvent> {
set(value) {
jo.put("notes", value)
}
var signature: String
get(): String = jo.optString("signature")
set(value) {
if (value.isNotEmpty())
jo.put("signature", value)
}
constructor(jo: JSONObject) {
this.jo = jo
@@ -98,8 +90,6 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_TEMPERATURE -> R.string.event_temperature_type
TYPE_COLIC -> R.string.event_colic_type
TYPE_FOOD -> R.string.event_food_type
TYPE_PUKE -> R.string.event_puke_type
TYPE_BATH -> R.string.event_bath_type
else -> R.string.event_unknown_type
}
)
@@ -121,8 +111,6 @@ class LunaEvent: Comparable<LunaEvent> {
TYPE_TEMPERATURE -> R.string.event_temperature_desc
TYPE_COLIC -> R.string.event_colic_desc
TYPE_FOOD -> R.string.event_food_desc
TYPE_PUKE -> R.string.event_puke_desc
TYPE_BATH -> R.string.event_bath_desc
else -> R.string.event_unknown_desc
}
)
@@ -140,7 +128,7 @@ class LunaEvent: Comparable<LunaEvent> {
}
override fun toString(): String {
return "$type qty: $quantity time: ${Date(time * 1000)}"
return "${type} qty: $quantity time: ${Date(time * 1000)}"
}
override fun compareTo(other: LunaEvent): Int {

View File

@@ -13,9 +13,9 @@ import java.io.FilenameFilter
class FileLogbookRepository: LogbookRepository {
companion object {
const val TAG = "FileLogbookRepository"
const val FILE_NAME_START = "data"
const val FILE_NAME_END = ".json"
val TAG = "FileLogbookRepository"
val FILE_NAME_START = "data"
val FILE_NAME_END = ".json"
}
override fun loadLogbook(context: Context, name: String, listener: LogbookLoadedListener) {
@@ -32,7 +32,7 @@ class FileLogbookRepository: LogbookRepository {
fun loadLogbook(context: Context, name: String): Logbook {
val logbook = Logbook(name)
val fileName = getFileName(name)
val file = File(context.filesDir, fileName)
val file = File(context.getFilesDir(), fileName)
val json = FileInputStream(file).bufferedReader().use { it.readText() }
val ja = JSONArray(json)
for (i in 0 until ja.length()) {
@@ -58,7 +58,7 @@ class FileLogbookRepository: LogbookRepository {
fun saveLogbook(context: Context, logbook: Logbook) {
val fileName = getFileName(logbook.name)
val file = File(context.filesDir, fileName)
val file = File(context.getFilesDir(), fileName)
val ja = JSONArray()
for (l in logbook.logs) {
ja.put(l.toJson())
@@ -82,7 +82,7 @@ class FileLogbookRepository: LogbookRepository {
}
private fun listLogbooks(context: Context): ArrayList<String> {
val logbooksFileNames = context.filesDir.list(object: FilenameFilter {
val logbooksFileNames = context.getFilesDir().list(object: FilenameFilter {
override fun accept(dir: File?, name: String?): Boolean {
if (name == null)
return false

View File

@@ -7,14 +7,13 @@ import androidx.core.content.edit
class LocalSettingsRepository(val context: Context) {
companion object {
const val SHARED_PREFS_FILE_NAME = "lunasettings"
const val SHARED_PREFS_BB_CONTENT = "bbcontent"
const val SHARED_PREFS_DATA_REPO = "data_repo"
const val SHARED_PREFS_DAV_URL = "webdav_url"
const val SHARED_PREFS_DAV_USER = "webdav_user"
const val SHARED_PREFS_DAV_PASS = "webdav_password"
const val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
const val SHARED_PREFS_SIGNATURE = "signature"
val SHARED_PREFS_FILE_NAME = "lunasettings"
val SHARED_PREFS_BB_CONTENT = "bbcontent"
val SHARED_PREFS_DATA_REPO = "data_repo"
val SHARED_PREFS_DAV_URL = "webdav_url"
val SHARED_PREFS_DAV_USER = "webdav_user"
val SHARED_PREFS_DAV_PASS = "webdav_password"
val SHARED_PREFS_NO_BREASTFEEDING = "no_breastfeeding"
}
enum class DATA_REPO {LOCAL_FILE, WEBDAV}
val sharedPreferences: SharedPreferences
@@ -24,23 +23,15 @@ class LocalSettingsRepository(val context: Context) {
}
fun saveBabyBottleContent(content: Int) {
sharedPreferences.edit { putInt(SHARED_PREFS_BB_CONTENT, content) }
sharedPreferences.edit().putInt(SHARED_PREFS_BB_CONTENT, content).apply()
}
fun loadBabyBottleContent(): Int {
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
}
fun saveSignature(content: String) {
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
}
fun loadSignature(): String {
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
}
fun saveNoBreastfeeding(content: Boolean) {
sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
sharedPreferences.edit().putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content).apply()
}
fun loadNoBreastfeeding(): Boolean {
@@ -48,15 +39,15 @@ class LocalSettingsRepository(val context: Context) {
}
fun saveDataRepository(repo: DATA_REPO) {
sharedPreferences.edit(commit = true) {
putString(
val spe = sharedPreferences.edit()
spe.putString(
SHARED_PREFS_DATA_REPO,
when (repo) {
DATA_REPO.WEBDAV -> "webdav"
DATA_REPO.LOCAL_FILE -> "localfile"
}
)
}
spe.commit()
}
fun loadDataRepository(): DATA_REPO {
@@ -69,11 +60,11 @@ class LocalSettingsRepository(val context: Context) {
}
fun saveWebdavCredentials(url: String, username: String, password: String) {
sharedPreferences.edit(commit = true) {
putString(SHARED_PREFS_DAV_URL, url)
putString(SHARED_PREFS_DAV_USER, username)
putString(SHARED_PREFS_DAV_PASS, password)
}
val spe = sharedPreferences.edit()
spe.putString(SHARED_PREFS_DAV_URL, url)
spe.putString(SHARED_PREFS_DAV_USER, username)
spe.putString(SHARED_PREFS_DAV_PASS, password)
spe.commit()
}
fun loadWebdavCredentials(): Array<String>? {

View File

@@ -1,77 +1,15 @@
package utils
import android.content.Context
import android.os.Build
import android.text.format.DateFormat
import it.danieleverducci.lunatracker.R
import java.util.Date
class DateUtils {
companion object {
/**
* Format time duration in seconds as e.g. "2 hours, 1 min".
* Used for the duration to the next/previous event in the event details dialog.
*/
fun formatTimeDuration(context: Context, secondsDiff: Long): String {
var seconds = secondsDiff
val years = (seconds / (365 * 24 * 60 * 60F)).toLong()
seconds -= years * (365 * 24 * 60 * 60)
val days = (seconds / (24 * 60 * 60F)).toLong()
seconds -= days * (24 * 60 * 60)
val hours = (seconds / (60 * 60F)).toLong()
seconds -= hours * (60 * 60)
val minutes = (seconds / 60F).toLong()
seconds -= minutes * 60
fun format(value1: Long, value2: Long, resIdSingular1: Int, resIdPlural1: Int, resIdSingular2: Int, resIdPlural2: Int): String {
val builder = StringBuilder()
if (value1 == 0L) {
// omit
} else if (value1 == 1L) {
builder.append(value1)
builder.append(" ")
builder.append(context.getString(resIdSingular1))
} else {
builder.append(value1)
builder.append(" ")
builder.append(context.getString(resIdPlural1))
}
if (value1 > 0L && value2 > 0L) {
builder.append(", ")
}
if (value2 == 0L) {
// omit
} else if (value2 == 1L) {
builder.append(value2)
builder.append(" ")
builder.append(context.getString(resIdSingular2))
} else {
builder.append(value2)
builder.append(" ")
builder.append(context.getString(resIdPlural2))
}
return builder.toString()
}
if (years > 0) {
return format(years, days, R.string.year_ago, R.string.years_ago, R.string.day_ago, R.string.days_ago)
} else if (days > 0) {
return format(days, hours, R.string.day_ago, R.string.days_ago, R.string.hour_ago, R.string.hours_ago)
} else if (hours > 0) {
return format(hours, minutes, R.string.hour_ago, R.string.hours_ago, R.string.minute_ago, R.string.minutes_ago)
} else if (minutes > 0) {
return format(minutes, seconds, R.string.minute_ago, R.string.minute_ago, R.string.second_ago, R.string.seconds_ago)
} else {
return context.getString(R.string.now)
}
}
/**
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago".
* Used for the event list.
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago)
*/
fun formatTimeAgo(context: Context, unixTime: Long): String {
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
@@ -87,10 +25,10 @@ class DateUtils {
return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" +
DateFormat.getTimeFormat(context).format(Date(unixTime*1000))
val formattedTime = StringBuilder()
var formattedTime = StringBuilder()
if (hoursAgo > 0) {
formattedTime.append(hoursAgo).append(" ")
if (hoursAgo == 1)
if (hoursAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.hour_ago))
else
formattedTime.append(context.getString(R.string.hours_ago))
@@ -99,28 +37,12 @@ class DateUtils {
if (formattedTime.isNotEmpty())
formattedTime.append(", ")
formattedTime.append(minutesAgo).append(" ")
if (minutesAgo == 1)
if (minutesAgo.toInt() == 1)
formattedTime.append(context.getString(R.string.minute_ago))
else
formattedTime.append(context.getString(R.string.minutes_ago))
}
return formattedTime.toString()
}
/**
* Format time as localized string without seconds. E.g. "Sept 18, 2025, 03:36 PM".
* Used in the event detail dialog.
*/
fun formatDateTime(unixTime: Long): String {
val date = Date(unixTime * 1000)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val dateFormat = android.icu.text.DateFormat.getDateTimeInstance(android.icu.text.DateFormat.DEFAULT, android.icu.text.DateFormat.SHORT)
return dateFormat.format(date)
} else {
// fallback
val dateFormat = java.text.DateFormat.getDateTimeInstance()
return dateFormat.format(date)
}
}
}
}

View File

@@ -3,7 +3,6 @@ package utils
import android.content.Context
import android.icu.util.LocaleData
import android.icu.util.ULocale
import android.os.Build
import it.danieleverducci.lunatracker.R
import it.danieleverducci.lunatracker.entities.LunaEvent
import java.text.NumberFormat
@@ -15,45 +14,29 @@ class NumericUtils (val context: Context) {
val measurement_unit_weight_tiny: String
val measurement_unit_temperature_base: String
private fun isMetricSystem(): Boolean {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
return (measurementSystem == LocaleData.MeasurementSystem.SI)
} else {
val locale = context.resources.configuration.locale
return when (locale.country) {
// https://en.wikipedia.org/wiki/United_States_customary_units
// https://en.wikipedia.org/wiki/Imperial_units
"US" -> false // US IMPERIAL
// UK, Myanmar, Liberia,
"GB", "MM", "LR" -> false // IMPERIAL
else -> true // METRIC
}
}
}
init {
this.numberFormat = NumberFormat.getInstance()
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
this.measurement_unit_liquid_base = context.getString(
if (isMetricSystem())
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_liquid_base_metric
else
R.string.measurement_unit_liquid_base_imperial
)
this.measurement_unit_weight_base = context.getString(
if (isMetricSystem())
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_weight_base_metric
else
R.string.measurement_unit_weight_base_imperial
)
this.measurement_unit_weight_tiny = context.getString(
if (isMetricSystem())
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_weight_tiny_metric
else
R.string.measurement_unit_weight_tiny_imperial
)
this.measurement_unit_temperature_base = context.getString(
if (isMetricSystem())
if (measurementSystem == LocaleData. MeasurementSystem.SI)
R.string.measurement_unit_temperature_base_metric
else
R.string.measurement_unit_temperature_base_imperial
@@ -62,15 +45,11 @@ class NumericUtils (val context: Context) {
fun formatEventQuantity(item: LunaEvent): String {
val formatted = StringBuilder()
if (item.quantity > 0) {
formatted.append(when (item.type) {
LunaEvent.TYPE_TEMPERATURE ->
(item.quantity / 10.0f).toString()
LunaEvent.TYPE_PUKE ->
context.resources.getStringArray(R.array.AmountLabels)[item.quantity]
else ->
item.quantity
})
if ((item.quantity ?: 0) > 0) {
if (item.type == LunaEvent.TYPE_TEMPERATURE)
formatted.append((item.quantity / 10.0f).toString())
else
formatted.append(item.quantity)
formatted.append(" ")
formatted.append(
@@ -91,9 +70,10 @@ class NumericUtils (val context: Context) {
* @return min, max, normal
*/
fun getValidEventQuantityRange(lunaEventType: String): Triple<Int, Int, Int>? {
val measurementSystem = LocaleData.getMeasurementSystem(ULocale.getDefault())
return when (lunaEventType) {
LunaEvent.TYPE_TEMPERATURE -> {
if (isMetricSystem())
if (measurementSystem == LocaleData. MeasurementSystem.SI)
Triple(
context.resources.getInteger(R.integer.human_body_temp_min_metric),
context.resources.getInteger(R.integer.human_body_temp_max_metric),

View File

@@ -47,7 +47,7 @@
<LinearLayout
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:orientation="horizontal"
android:gravity="center_vertical">
@@ -95,7 +95,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
android:textSize="50sp"
@@ -106,7 +106,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
android:textSize="50sp"
@@ -123,7 +123,7 @@
android:id="@+id/button_nipple_left"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -134,7 +134,7 @@
android:id="@+id/button_nipple_both"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -145,7 +145,7 @@
android:id="@+id/button_nipple_right"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -162,7 +162,7 @@
android:id="@+id/button_change_poo"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="2"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -173,7 +173,7 @@
android:id="@+id/button_change_pee"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="2"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -184,7 +184,7 @@
android:id="@+id/button_more"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="5dp"
android:layout_margin="10dp"
android:layout_weight="1"
android:background="@drawable/button_background"
android:gravity="center_horizontal"
@@ -195,31 +195,21 @@
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/logbook"
android:textColor="@color/accent"
android:textStyle="bold"/>
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_indicator"
android:layout_width="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:layout_gravity="center"
android:layout_marginHorizontal="10dp"
android:layout_margin="10dp"
android:indeterminate="true"
app:indicatorColor="@color/accent"
android:visibility="invisible"/>
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/logbook"
android:textColor="@color/accent"
android:textStyle="bold"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_events"

View File

@@ -45,7 +45,7 @@
<RadioButton android:id="@+id/settings_data_webdav"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginTop="30dp"
android:text="@string/settings_storage_dav"/>
<TextView
@@ -119,57 +119,27 @@
android:visibility="invisible"/>
</RadioGroup>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/settings_signature" />
<EditText
android:id="@+id/settings_signature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="5dp"
android:inputType="textEmailAddress"
android:background="@drawable/textview_background"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="5dp"
android:text="@string/settings_signature_desc"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="20dp">
android:layout_marginEnd="30dp">
<TextView
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:textStyle="bold"
android:text="@string/settings_no_breastfeeding" />
android:text="@string/no_breastfeeding" />
<com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_no_breastfeeding"
android:layout_width="wrap_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_weight="1" />
</LinearLayout>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="5dp"
android:text="@string/settings_no_breastfeeding_desc"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"

View File

@@ -1,30 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="20dp"
android:paddingBottom="10dp"
android:paddingHorizontal="20dp">
android:padding="20dp">
<TextView
android:id="@+id/dialog_event_detail_type_emoji"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textSize="60sp"
android:text="@string/event_diaperchange_pee_type" />
android:textSize="60dp"
android:text="@string/event_diaperchange_pee_type"/>
<TextView
android:id="@+id/dialog_event_detail_type_description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:textColor="@color/accent"
android:textSize="32sp"
android:text="@string/event_diaperchange_pee_desc" />
android:textSize="24dp"
android:text="@string/event_diaperchange_pee_desc"/>
<TextView
android:id="@+id/dialog_event_detail_type_date"
@@ -33,20 +30,18 @@
android:layout_marginTop="20dp"
android:layout_gravity="center_horizontal"
android:gravity="center_vertical"
android:drawableEnd="@drawable/ic_edit"
android:drawablePadding="10dp"
android:drawableTint="@color/accent"
android:textSize="16sp"
android:textStyle="bold"
android:text="@string/dialog_event_detail_datetime_icon"
app:drawableEndCompat="@drawable/ic_edit" />
android:text="@string/dialog_event_detail_datetime_icon"/>
<TextView
android:id="@+id/dialog_event_detail_type_quantity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:textSize="28sp"
android:text="@string/dialog_event_detail_quantity"/>
android:text="Quantity"/>
<ScrollView
android:layout_width="match_parent"
@@ -58,45 +53,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textStyle="italic"
android:textSize="20sp"
android:text="@string/dialog_event_detail_notes"/>
android:text="Notes"/>
</ScrollView>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:id="@+id/dialog_event_detail_type_signature"
android:layout_marginBottom="5dp"
android:visibility="gone"/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/dialog_event_previous"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:textSize="12sp"
android:text="" />
<Space
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />
<TextView
android:id="@+id/dialog_event_next"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:textSize="12sp"
android:text="" />
</LinearLayout>
</LinearLayout>

View File

@@ -2,7 +2,7 @@
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:padding="20dp"
android:background="@color/transparent">
<LinearLayout
@@ -14,17 +14,27 @@
android:id="@+id/button_medicine"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="10dp"
android:padding="20dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_medicine"/>
<TextView
android:id="@+id/button_enema"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="20dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_enema"/>
<TextView
android:id="@+id/button_note"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:padding="20dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_note"/>
@@ -34,27 +44,17 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:padding="20dp"
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:padding="20dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_colic"/>
@@ -64,31 +64,11 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:padding="20dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_scale"/>
<TextView
android:id="@+id/button_bath"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_bath"/>
<TextView
android:id="@+id/button_enema"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="10dp"
android:padding="10dp"
android:background="@drawable/dropdown_list_item_background"
style="@style/OverflowMenuText"
android:text="@string/overflow_event_enema"/>
</LinearLayout>
</ScrollView>

View File

@@ -1,17 +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"
android:orientation="vertical">
<Spinner
android:id="@+id/dialog_puke_value"
android:layout_width="250dp"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp"/>
</LinearLayout>

View File

@@ -11,7 +11,7 @@
android:id="@+id/type"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:paddingStart="10dp"
android:paddingLeft="10dp"
android:textSize="28sp"
android:lines="1"
android:maxLines="1"
@@ -25,7 +25,7 @@
android:ellipsize="end"
android:maxLines="2"
android:textColor="@color/accent"
android:text="@string/row_luna_event_description"/>
android:text="Description"/>
<TextView
android:id="@+id/quantity"
@@ -35,7 +35,7 @@
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center_horizontal"
android:text="@string/row_luna_event_quantity"/>
android:text="Qty"/>
<TextView
android:id="@+id/time"
@@ -46,6 +46,6 @@
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/row_luna_event_time"/>
android:text="Time"/>
</LinearLayout>

View File

@@ -31,7 +31,7 @@
android:maxLines="2"
android:gravity="center_horizontal"
android:textColor="@color/accent"
android:text="@string/row_luna_event_description"/>
android:text="Description"/>
<TextView
android:id="@+id/quantity"
@@ -41,7 +41,7 @@
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:gravity="center_horizontal"
android:text="@string/dialog_event_detail_quantity"/>
android:text="Qty"/>
<TextView
android:id="@+id/time"
@@ -52,7 +52,7 @@
android:textStyle="bold"
android:ellipsize="end"
android:maxLines="2"
android:text="@string/row_luna_event_time"/>
android:text="Time"/>
</LinearLayout>

View File

@@ -50,8 +50,9 @@
<string name="no_connection_go_to_settings">Einstellungen</string>
<string name="no_connection_retry">Erneut versuchen</string>
<string name="no_breastfeeding">Kein Stillen</string>
<string name="settings_title">Einstellungen</string>
<string name="settings_no_breastfeeding">Kein Stillen</string>
<string name="settings_storage">Speicherort für Daten auswählen</string>
<string name="settings_storage_local">Auf dem Gerät</string>
<string name="settings_storage_local_desc">Datenschutzfreundlichste Lösung: Deine Daten verlassen dein Gerät nicht</string>

View File

@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="AmountLabels">
<item>@string/amount_little</item>
<item>@string/amount_normal</item>
<item>@string/amount_plenty</item>
</string-array>
</resources>

View File

@@ -12,9 +12,6 @@
<string name="log_temperature_dialog_title">Temperature</string>
<string name="log_temperature_dialog_description">Insert the temperature</string>
<string name="log_puke_dialog_title">Puke</string>
<string name="log_puke_dialog_description">Select the amount</string>
<string name="event_bottle_type" translatable="false">🍼</string>
<string name="event_food_type" translatable="false">🥣</string>
<string name="event_scale_type" translatable="false">⚖️</string>
@@ -28,8 +25,6 @@
<string name="event_note_type" translatable="false">📝</string>
<string name="event_temperature_type" translatable="false">🌡️</string>
<string name="event_colic_type" translatable="false">💨</string>
<string name="event_puke_type" translatable="false">🤮</string>
<string name="event_bath_type" translatable="false">🛁</string>
<string name="event_unknown_type" translatable="false">\?</string>
<string name="event_bottle_desc">Baby bottle</string>
@@ -45,8 +40,6 @@
<string name="event_note_desc">Note</string>
<string name="event_temperature_desc">Temperature</string>
<string name="event_colic_desc">Gaseous colic</string>
<string name="event_puke_desc">Puke</string>
<string name="event_bath_desc">Bath</string>
<string name="event_unknown_desc"></string>
<string name="overflow_event_scale">⚖️ Weight</string>
@@ -55,8 +48,6 @@
<string name="overflow_event_note">📝 Note</string>
<string name="overflow_event_temperature">🌡️ Temperature</string>
<string name="overflow_event_colic">💨 Gaseous colic</string>
<string name="overflow_event_puke">🤮 Puke</string>
<string name="overflow_event_bath">🛁 Bath</string>
<string name="toast_event_added">Event logged</string>
<string name="toast_logbook_saved">Logbook saved</string>
@@ -64,29 +55,19 @@
<string name="toast_integer_error">Invalid value. Insert an integer.</string>
<string name="now">now</string>
<string name="second_ago">sec</string>
<string name="seconds_ago">secs</string>
<string name="minute_ago">min</string>
<string name="minutes_ago">mins</string>
<string name="hour_ago">hour</string>
<string name="hours_ago">hours</string>
<string name="day_ago">day</string>
<string name="days_ago">days</string>
<string name="year_ago">year</string>
<string name="years_ago">years</string>
<string name="amount_little">Little</string>
<string name="amount_normal">Normal</string>
<string name="amount_plenty">Plenty</string>
<string name="minute_ago">min</string>
<string name="minutes_ago">mins</string>
<string name="no_connection">No connection</string>
<string name="no_connection_explain">Unable to reach WebDAV service</string>
<string name="no_connection_go_to_settings">Settings</string>
<string name="no_connection_retry">Retry</string>
<string name="no_breastfeeding">No Breastfeeding</string>
<string name="settings_title">Settings</string>
<string name="settings_signature">Signature</string>
<string name="settings_signature_desc">Attach a signature to each event you create and for others to see. Useful if multiple people add events.</string>
<string name="settings_storage">Choose where to save data</string>
<string name="settings_storage_local">On device</string>
<string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string>
@@ -102,8 +83,6 @@
<string name="settings_webdav_error_generic">Error while trying to access WebDAV:</string>
<string name="settings_webdav_creation_error_generic">Unable to save a file on the WebDAV server:</string>
<string name="settings_webdav_creation_ok">Successfully connected with the WebDAV server</string>
<string name="settings_no_breastfeeding">No Breastfeeding</string>
<string name="settings_no_breastfeeding_desc">Hide the Breastfeeding buttons for when they are not needed.</string>
<string name="settings_json_error">There\'s a save file on the server, but it is corrupted or unreadable. Please delete it </string>
<string name="settings_generic_error">Error: </string>
<string name="settings_webdav_upload_error">Error while uploading local logbook %1$s to webdav: %2$s</string>
@@ -128,17 +107,10 @@
<string name="measurement_unit_temperature_base_imperial" translatable="false">°F</string>
<string name="measurement_unit_temperature_base_metric" translatable="false">°C</string>
<string name="row_luna_event_description">Description</string>
<string name="row_luna_event_quantity">Qty</string>
<string name="row_luna_event_time">Time</string>
<string name="dialog_event_detail_title">Event detail</string>
<string name="dialog_event_detail_datetime_icon" translatable="false">🕒 %s</string>
<string name="dialog_event_detail_datetime_icon" translatable="false">🕒 %s1</string>
<string name="dialog_event_detail_close_button">OK</string>
<string name="dialog_event_detail_delete_button">Delete</string>
<string name="dialog_event_detail_quantity">Quantity</string>
<string name="dialog_event_detail_notes">Notes</string>
<string name="dialog_event_detail_signature">by %s</string>
<string name="dialog_add_logbook_title">Add logbook</string>
<string name="dialog_add_logbook_logbookname">👶 Logbook name</string>

View File

@@ -6,7 +6,6 @@ This app is meant to log all the relevant events (diaper change, breastfeeding,
Dedicated to my daughter Luna.
A HUGE thanks to all our contributors. See https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
NOTE: The content on this app is for informational or educational purposes only and does not substitute professional medical advice or consultations with healthcare professionals.
Feature graphic ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons

View File

@@ -8,8 +8,6 @@ Elle permet de synchroniser les données entre différents appareils (en utilisa
Dédié à ma fille Luna.
Un grand merci à tous nos contributeurs. Voir https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
REMARQUE : le contenu de cette application est fourni à titre informatif ou éducatif uniquement et ne remplace pas les conseils médicaux professionnels ou les consultations avec des professionnels de la santé.
Crédit image ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons

View File

@@ -6,7 +6,6 @@ Quest'app è pensata per memorizzare tutti gli eventi del bambino (cambio di pan
Dedicato a mia figlia Luna.
Un grandissimo ringraziamento a tutti i contributors! Vedi https://git.ichibi.eu/penguin86/luna-tracker/src/branch/master/README.md
NOTA: il contenuto di quest'app ha solo scopo informativo o didattico e non sostituisce il consulto medico professionale o le consulenze con operatori sanitari.
Feature graphic ("Baby and baby milk bottle. Baby feeding."): © Vyacheslav Argenberg / http://www.vascoplanet.com/, CC BY 4.0 <https://creativecommons.org/licenses/by/4.0>, via Wikimedia Commons

View File

@@ -1,5 +1,5 @@
[versions]
agp = "8.13.0"
agp = "8.7.2"
kotlin = "2.0.0"
coreKtx = "1.10.1"
junit = "4.13.2"

View File

@@ -1,6 +1,6 @@
#Sat Nov 02 10:58:51 CET 2024
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists