3 Commits

Author SHA1 Message Date
d6b9df757c activity_setting: fine tune layout style 2025-09-29 09:13:09 +02:00
84bdbdf847 add signature setting
For multiple users it helps to
keep track about who did what.
2025-09-29 09:13:09 +02:00
ff0d4df2de DateUtils: move event details formatting to DateUtils
Also display second as 0 since it is easier
to read and does not have meaning for the user.
2025-09-29 09:13:05 +02:00
8 changed files with 110 additions and 12 deletions

View File

@@ -13,6 +13,7 @@ import android.view.ViewGroup
import android.widget.AdapterView import android.widget.AdapterView
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
import android.widget.EditText import android.widget.EditText
import android.widget.LinearLayout
import android.widget.NumberPicker import android.widget.NumberPicker
import android.widget.PopupWindow import android.widget.PopupWindow
import android.widget.Spinner import android.widget.Spinner
@@ -41,7 +42,6 @@ import okio.IOException
import org.json.JSONException import org.json.JSONException
import utils.DateUtils import utils.DateUtils
import utils.NumericUtils import utils.NumericUtils
import java.text.DateFormat
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
@@ -58,6 +58,7 @@ class MainActivity : AppCompatActivity() {
lateinit var buttonsContainer: ViewGroup lateinit var buttonsContainer: ViewGroup
lateinit var recyclerView: RecyclerView lateinit var recyclerView: RecyclerView
lateinit var handler: Handler lateinit var handler: Handler
var signature = ""
var savingEvent = false var savingEvent = false
val updateListRunnable: Runnable = Runnable { val updateListRunnable: Runnable = Runnable {
if (logbook != null && !pauseLogbookUpdate) if (logbook != null && !pauseLogbookUpdate)
@@ -169,6 +170,8 @@ class MainActivity : AppCompatActivity() {
logbookRepo = FileLogbookRepository() logbookRepo = FileLogbookRepository()
} }
signature = settingsRepository.loadSignature()
val noBreastfeeding = settingsRepository.loadNoBreastfeeding() val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) { findViewById<View>(R.id.layout_nipples).visibility = when (noBreastfeeding) {
true -> View.GONE true -> View.GONE
@@ -340,7 +343,6 @@ class MainActivity : AppCompatActivity() {
fun showEventDetailDialog(event: LunaEvent, items: ArrayList<LunaEvent>) { fun showEventDetailDialog(event: LunaEvent, items: ArrayList<LunaEvent>) {
// Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user // Do not update list while the detail is shown, to avoid changing the object below while it is changed by the user
pauseLogbookUpdate = true pauseLogbookUpdate = true
val dateFormat = DateFormat.getDateTimeInstance()
val d = AlertDialog.Builder(this) val d = AlertDialog.Builder(this)
d.setTitle(R.string.dialog_event_detail_title) d.setTitle(R.string.dialog_event_detail_title)
val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null) val dialogView = layoutInflater.inflate(R.layout.dialog_event_detail, null)
@@ -352,8 +354,9 @@ class MainActivity : AppCompatActivity() {
val currentDateTime = Calendar.getInstance() val currentDateTime = Calendar.getInstance()
currentDateTime.time = Date(event.time * 1000) currentDateTime.time = Date(event.time * 1000)
val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date) val dateTextView = dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_date)
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time)) dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
dateTextView.setOnClickListener { dateTextView.setOnClickListener {
// Show datetime picker // Show datetime picker
val startYear = currentDateTime.get(Calendar.YEAR) val startYear = currentDateTime.get(Calendar.YEAR)
@@ -366,11 +369,9 @@ class MainActivity : AppCompatActivity() {
TimePickerDialog(this, { _, hour, minute -> TimePickerDialog(this, { _, hour, minute ->
val pickedDateTime = Calendar.getInstance() val pickedDateTime = Calendar.getInstance()
pickedDateTime.set(year, month, day, hour, minute) 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 // Save event and move it to the right position in the logbook
event.time = currentDateTime.time.time / 1000 // Seconds since epoch event.time = pickedDateTime.time.time / 1000 // Seconds since epoch
dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), DateUtils.formatDateTime(event.time))
logbook?.sort() logbook?.sort()
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
saveLogbook() saveLogbook()
@@ -389,6 +390,14 @@ class MainActivity : AppCompatActivity() {
pauseLogbookUpdate = false pauseLogbookUpdate = false
}) })
// show optional signature
dialogView.findViewById<TextView>(R.id.dialog_event_detail_type_signature).text = event.signature
dialogView.findViewById<LinearLayout>(R.id.dialog_event_signature_layout).visibility = if (event.signature.isNotEmpty()) {
View.VISIBLE
} else {
View.GONE
}
// create next/previous links to events of the same type // create next/previous links to events of the same type
val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous) val previousTextView = dialogView.findViewById<TextView>(R.id.dialog_event_previous)
@@ -638,6 +647,8 @@ class MainActivity : AppCompatActivity() {
fun logEvent(event: LunaEvent) { fun logEvent(event: LunaEvent) {
savingEvent(true) savingEvent(true)
event.signature = signature
setLoading(true) setLoading(true)
logbook?.logs?.add(0, event) logbook?.logs?.add(0, event)
recyclerView.adapter?.notifyItemInserted(0) recyclerView.adapter?.notifyItemInserted(0)
@@ -788,7 +799,9 @@ class MainActivity : AppCompatActivity() {
dismiss() dismiss()
}) })
contentView.findViewById<View>(R.id.button_puke).setOnClickListener({ contentView.findViewById<View>(R.id.button_puke).setOnClickListener({
askNotes(LunaEvent(LunaEvent.TYPE_PUKE)) logEvent(
LunaEvent(LunaEvent.TYPE_PUKE)
)
dismiss() dismiss()
}) })
contentView.findViewById<View>(R.id.button_colic).setOnClickListener({ contentView.findViewById<View>(R.id.button_colic).setOnClickListener({

View File

@@ -2,6 +2,7 @@ package it.danieleverducci.lunatracker
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.EditText
import android.widget.RadioButton import android.widget.RadioButton
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
@@ -24,6 +25,7 @@ open class SettingsActivity : AppCompatActivity() {
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 switchNoBreastfeeding: SwitchMaterial
protected lateinit var textViewSignature: EditText
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@@ -36,6 +38,7 @@ open class SettingsActivity : AppCompatActivity() {
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) switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding)
textViewSignature = findViewById(R.id.settings_signature)
findViewById<View>(R.id.settings_save).setOnClickListener({ findViewById<View>(R.id.settings_save).setOnClickListener({
validateAndSave() validateAndSave()
@@ -52,12 +55,14 @@ open class SettingsActivity : AppCompatActivity() {
val dataRepo = settingsRepository.loadDataRepository() val dataRepo = settingsRepository.loadDataRepository()
val webDavCredentials = settingsRepository.loadWebdavCredentials() val webDavCredentials = settingsRepository.loadWebdavCredentials()
val noBreastfeeding = settingsRepository.loadNoBreastfeeding() val noBreastfeeding = settingsRepository.loadNoBreastfeeding()
val signature = settingsRepository.loadSignature()
when (dataRepo) { when (dataRepo) {
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true
LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true
} }
textViewSignature.setText(signature)
switchNoBreastfeeding.isChecked = noBreastfeeding switchNoBreastfeeding.isChecked = noBreastfeeding
if (webDavCredentials != null) { if (webDavCredentials != null) {
@@ -156,6 +161,7 @@ open class SettingsActivity : AppCompatActivity() {
else LocalSettingsRepository.DATA_REPO.LOCAL_FILE else LocalSettingsRepository.DATA_REPO.LOCAL_FILE
) )
settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked) settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked)
settingsRepository.saveSignature(textViewSignature.text.toString())
settingsRepository.saveWebdavCredentials( settingsRepository.saveWebdavCredentials(
textViewWebDAVUrl.text.toString(), textViewWebDAVUrl.text.toString(),
textViewWebDAVUser.text.toString(), textViewWebDAVUser.text.toString(),

View File

@@ -55,6 +55,12 @@ class LunaEvent: Comparable<LunaEvent> {
set(value) { set(value) {
jo.put("notes", value) jo.put("notes", value)
} }
var signature: String
get(): String = jo.optString("signature")
set(value) {
if (value.isNotEmpty())
jo.put("signature", value)
}
constructor(jo: JSONObject) { constructor(jo: JSONObject) {
this.jo = jo this.jo = jo

View File

@@ -14,6 +14,7 @@ class LocalSettingsRepository(val context: Context) {
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_NO_BREASTFEEDING = "no_breastfeeding"
const val SHARED_PREFS_SIGNATURE = "signature"
} }
enum class DATA_REPO {LOCAL_FILE, WEBDAV} enum class DATA_REPO {LOCAL_FILE, WEBDAV}
val sharedPreferences: SharedPreferences val sharedPreferences: SharedPreferences
@@ -30,6 +31,14 @@ class LocalSettingsRepository(val context: Context) {
return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1) return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1)
} }
fun saveSignature(content: String) {
sharedPreferences.edit { putString(SHARED_PREFS_SIGNATURE, content) }
}
fun loadSignature(): String {
return sharedPreferences.getString(SHARED_PREFS_SIGNATURE, "") ?: ""
}
fun saveNoBreastfeeding(content: Boolean) { fun saveNoBreastfeeding(content: Boolean) {
sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) } sharedPreferences.edit { putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content) }
} }

View File

@@ -7,6 +7,10 @@ import java.util.Date
class DateUtils { class DateUtils {
companion object { companion object {
/**
* Format time duration in seconds as e.g. "2 hours, 1 min".
* Used for the duration to the next/previous event in the event details dialog.
*/
fun formatTimeDuration(context: Context, secondsDiff: Long): String { fun formatTimeDuration(context: Context, secondsDiff: Long): String {
var seconds = secondsDiff var seconds = secondsDiff
@@ -65,7 +69,8 @@ class DateUtils {
} }
/** /**
* Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago) * Formats the provided unix timestamp in a string like "3 hours, 26 minutes ago".
* Used for the event list.
*/ */
fun formatTimeAgo(context: Context, unixTime: Long): String { fun formatTimeAgo(context: Context, unixTime: Long): String {
val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime val secondsDiff = (System.currentTimeMillis() / 1000) - unixTime
@@ -100,5 +105,17 @@ class DateUtils {
} }
return formattedTime.toString() return formattedTime.toString()
} }
/**
* Format time as localized string. E.g. "28 Sept 03:36:00".
* The seconds are set to 0 since they are distracting and not relevant.
* Used in the event detail dialog.
*/
fun formatDateTime(unixTime: Long): String {
val roundedUnixTime = unixTime - (unixTime % 60)
val date = Date(roundedUnixTime * 1000)
val dateFormat = java.text.DateFormat.getDateTimeInstance()
return dateFormat.format(date)
}
} }
} }

View File

@@ -119,12 +119,33 @@
android:visibility="invisible"/> android:visibility="invisible"/>
</RadioGroup> </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="20dp"
android:inputType="textEmailAddress"
android:background="@drawable/textview_background"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="30dp"
android:layout_marginTop="5dp"
android:text="@string/settings_signature_desc"/>
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:orientation="horizontal" android:orientation="horizontal"
android:layout_marginTop="5dp" android:layout_marginTop="20dp">
android:layout_marginEnd="30dp">
<TextView <TextView
android:layout_width="0dp" android:layout_width="0dp"
@@ -135,8 +156,9 @@
<com.google.android.material.switchmaterial.SwitchMaterial <com.google.android.material.switchmaterial.SwitchMaterial
android:id="@+id/switch_no_breastfeeding" android:id="@+id/switch_no_breastfeeding"
android:layout_width="0dp" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="20dp"
android:layout_weight="1" /> android:layout_weight="1" />
</LinearLayout> </LinearLayout>
@@ -145,6 +167,7 @@
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="30dp"
android:layout_marginTop="5dp"
android:text="@string/settings_no_breastfeeding_desc"/> android:text="@string/settings_no_breastfeeding_desc"/>
<LinearLayout <LinearLayout

View File

@@ -89,4 +89,25 @@
</LinearLayout> </LinearLayout>
<LinearLayout
android:id="@+id/dialog_event_signature_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="5dp"
android:text="@string/dialog_event_detail_signature"/>
<TextView
android:id="@+id/dialog_event_detail_type_signature"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="" />
</LinearLayout>
</LinearLayout> </LinearLayout>

View File

@@ -78,6 +78,8 @@
<string name="no_connection_retry">Retry</string> <string name="no_connection_retry">Retry</string>
<string name="settings_title">Settings</string> <string name="settings_title">Settings</string>
<string name="settings_signature">Signature</string>
<string name="settings_signature_desc">Attach a signature to each event your create and for others to see. Useful if multiple people add events.</string>
<string name="settings_storage">Choose where to save data</string> <string name="settings_storage">Choose where to save data</string>
<string name="settings_storage_local">On device</string> <string name="settings_storage_local">On device</string>
<string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string> <string name="settings_storage_local_desc">Most privacy-friendly solution: data doesn\'t leave your device</string>
@@ -129,6 +131,7 @@
<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>
<string name="dialog_event_detail_signature">Created By</string>
<string name="dialog_add_logbook_title">Add logbook</string> <string name="dialog_add_logbook_title">Add logbook</string>
<string name="dialog_add_logbook_logbookname">👶 Logbook name</string> <string name="dialog_add_logbook_logbookname">👶 Logbook name</string>