diff --git a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt index 1cb89dc..0d51fd0 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/MainActivity.kt @@ -39,6 +39,7 @@ 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 @@ -74,9 +75,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(R.id.progress_indicator) + buttonsContainer = findViewById(R.id.buttons_container) + recyclerView = findViewById(R.id.list_events) recyclerView.setLayoutManager(LinearLayoutManager(applicationContext)) // Set listeners @@ -131,7 +132,7 @@ class MainActivity : AppCompatActivity() { val adapter = LunaEventRecyclerAdapter(this, items) adapter.onItemClickListener = object: LunaEventRecyclerAdapter.OnItemClickListener{ override fun onItemClick(event: LunaEvent) { - showEventDetailDialog(event) + showEventDetailDialog(event, items) } } recyclerView.adapter = adapter @@ -168,6 +169,12 @@ class MainActivity : AppCompatActivity() { logbookRepo = FileLogbookRepository() } + val noBreastfeeding = settingsRepository.loadNoBreastfeeding() + findViewById(R.id.layout_nipples).visibility = when (noBreastfeeding) { + true -> View.GONE + false -> View.VISIBLE + } + // Update list dates recyclerView.adapter?.notifyDataSetChanged() @@ -302,25 +309,52 @@ class MainActivity : AppCompatActivity() { alertDialog.show() } - fun showEventDetailDialog(event: LunaEvent) { + fun getPreviousSameEvent(event: LunaEvent, items: ArrayList): 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? { + 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) { // 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 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(R.id.dialog_event_detail_type_emoji).setText(event.getTypeEmoji(this)) - dialogView.findViewById(R.id.dialog_event_detail_type_description).setText(event.getTypeDescription(this)) - dialogView.findViewById(R.id.dialog_event_detail_type_quantity).setText( + dialogView.findViewById(R.id.dialog_event_detail_type_emoji).text = event.getTypeEmoji(this) + dialogView.findViewById(R.id.dialog_event_detail_type_description).text = event.getTypeDescription(this) + dialogView.findViewById(R.id.dialog_event_detail_type_quantity).text = NumericUtils(this).formatEventQuantity(event) - ) - dialogView.findViewById(R.id.dialog_event_detail_type_notes).setText(event.notes) + dialogView.findViewById(R.id.dialog_event_detail_type_notes).text = event.notes val currentDateTime = Calendar.getInstance() currentDateTime.time = Date(event.time * 1000) val dateTextView = dialogView.findViewById(R.id.dialog_event_detail_type_date) dateTextView.text = String.format(getString(R.string.dialog_event_detail_datetime_icon), dateFormat.format(currentDateTime.time)) - dateTextView.setOnClickListener({ + dateTextView.setOnClickListener { // Show datetime picker val startYear = currentDateTime.get(Calendar.YEAR) val startMonth = currentDateTime.get(Calendar.MONTH) @@ -328,8 +362,8 @@ class MainActivity : AppCompatActivity() { val startHour = currentDateTime.get(Calendar.HOUR_OF_DAY) val startMinute = currentDateTime.get(Calendar.MINUTE) - DatePickerDialog(this, DatePickerDialog.OnDateSetListener { _, year, month, day -> - TimePickerDialog(this, TimePickerDialog.OnTimeSetListener { _, hour, minute -> + DatePickerDialog(this, { _, year, month, day -> + TimePickerDialog(this, { _, hour, minute -> val pickedDateTime = Calendar.getInstance() pickedDateTime.set(year, month, day, hour, minute) currentDateTime.time = pickedDateTime.time @@ -340,9 +374,9 @@ class MainActivity : AppCompatActivity() { logbook?.sort() recyclerView.adapter?.notifyDataSetChanged() saveLogbook() - }, startHour, startMinute, false).show() + }, startHour, startMinute, android.text.format.DateFormat.is24HourFormat(this@MainActivity)).show() }, startYear, startMonth, startDay).show() - }) + } d.setView(dialogView) d.setPositiveButton(R.string.dialog_event_detail_close_button) { dialogInterface, i -> dialogInterface.dismiss() } @@ -354,6 +388,37 @@ class MainActivity : AppCompatActivity() { // Resume logbook update pauseLogbookUpdate = false }) + + // create next/previous links to events of the same type + + val previousTextView = dialogView.findViewById(R.id.dialog_event_previous) + val nextTextView = dialogView.findViewById(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) { @@ -392,7 +457,7 @@ class MainActivity : AppCompatActivity() { sAdapter.setDropDownViewResource(R.layout.row_logbook_spinner) for (ln in logbooksNames) { sAdapter.add( - if (ln.isEmpty()) getString(R.string.default_logbook_name) else ln + ln.ifEmpty { getString(R.string.default_logbook_name) } ) } spinner.adapter = sAdapter @@ -410,7 +475,6 @@ class MainActivity : AppCompatActivity() { } override fun onNothingSelected(parent: AdapterView<*>?) {} - } }) } @@ -560,7 +624,6 @@ class MainActivity : AppCompatActivity() { onRepoError(getString(R.string.settings_generic_error) + error.toString()) }) } - }) } @@ -708,10 +771,10 @@ class MainActivity : AppCompatActivity() { isOutsideTouchable = true val inflater = LayoutInflater.from(anchor.context) contentView = inflater.inflate(R.layout.more_events_popup, null) - contentView.findViewById(R.id.button_medicine).setOnClickListener({ + contentView.findViewById(R.id.button_medicine).setOnClickListener { askNotes(LunaEvent(LunaEvent.TYPE_MEDICINE)) dismiss() - }) + } contentView.findViewById(R.id.button_enema).setOnClickListener({ logEvent(LunaEvent(LunaEvent.TYPE_ENEMA)) dismiss() diff --git a/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt b/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt index 13d2440..0cbd0cf 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/SettingsActivity.kt @@ -7,14 +7,13 @@ import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import com.google.android.material.progressindicator.LinearProgressIndicator +import com.google.android.material.switchmaterial.SwitchMaterial import com.thegrizzlylabs.sardineandroid.impl.SardineException import it.danieleverducci.lunatracker.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 @@ -24,6 +23,7 @@ open class SettingsActivity : AppCompatActivity() { protected lateinit var textViewWebDAVUser: TextView protected lateinit var textViewWebDAVPass: TextView protected lateinit var progressIndicator: LinearProgressIndicator + protected lateinit var switchNoBreastfeeding: SwitchMaterial override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -35,6 +35,8 @@ open class SettingsActivity : AppCompatActivity() { textViewWebDAVUser = findViewById(R.id.settings_data_webdav_user) textViewWebDAVPass = findViewById(R.id.settings_data_webdav_pass) progressIndicator = findViewById(R.id.progress_indicator) + switchNoBreastfeeding = findViewById(R.id.switch_no_breastfeeding) + findViewById(R.id.settings_save).setOnClickListener({ validateAndSave() }) @@ -49,11 +51,15 @@ open class SettingsActivity : AppCompatActivity() { fun loadSettings() { val dataRepo = settingsRepository.loadDataRepository() val webDavCredentials = settingsRepository.loadWebdavCredentials() + val noBreastfeeding = settingsRepository.loadNoBreastfeeding() when (dataRepo) { LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> radioDataLocal.isChecked = true LocalSettingsRepository.DATA_REPO.WEBDAV -> radioDataWebDAV.isChecked = true } + + switchNoBreastfeeding.isChecked = noBreastfeeding + if (webDavCredentials != null) { textViewWebDAVUrl.setText(webDavCredentials[0]) textViewWebDAVUser.setText(webDavCredentials[1]) @@ -149,6 +155,7 @@ open class SettingsActivity : AppCompatActivity() { if (radioDataWebDAV.isChecked) LocalSettingsRepository.DATA_REPO.WEBDAV else LocalSettingsRepository.DATA_REPO.LOCAL_FILE ) + settingsRepository.saveNoBreastfeeding(switchNoBreastfeeding.isChecked) settingsRepository.saveWebdavCredentials( textViewWebDAVUrl.text.toString(), textViewWebDAVUser.text.toString(), diff --git a/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt b/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt index cc9e29c..6b4fe90 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/entities/Logbook.kt @@ -2,7 +2,7 @@ package it.danieleverducci.lunatracker.entities class Logbook(val name: String) { companion object { - val MAX_SAFE_LOGBOOK_SIZE = 30000 + const val MAX_SAFE_LOGBOOK_SIZE = 30000 } val logs = ArrayList() diff --git a/app/src/main/java/it/danieleverducci/lunatracker/entities/LunaEvent.kt b/app/src/main/java/it/danieleverducci/lunatracker/entities/LunaEvent.kt index 0683958..30444c9 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/entities/LunaEvent.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/entities/LunaEvent.kt @@ -14,20 +14,20 @@ import java.util.Date class LunaEvent: Comparable { companion object { - val TYPE_BABY_BOTTLE = "BABY_BOTTLE" - val TYPE_WEIGHT = "WEIGHT" - val TYPE_BREASTFEEDING_LEFT_NIPPLE = "BREASTFEEDING_LEFT_NIPPLE" - val TYPE_BREASTFEEDING_BOTH_NIPPLE = "BREASTFEEDING_BOTH_NIPPLE" - val TYPE_BREASTFEEDING_RIGHT_NIPPLE = "BREASTFEEDING_RIGHT_NIPPLE" - val TYPE_DIAPERCHANGE_POO = "DIAPERCHANGE_POO" - val TYPE_DIAPERCHANGE_PEE = "DIAPERCHANGE_PEE" - 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" + 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" } private val jo: JSONObject diff --git a/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt b/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt index b48608c..ede0b93 100644 --- a/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt +++ b/app/src/main/java/it/danieleverducci/lunatracker/repository/LocalSettingsRepository.kt @@ -3,6 +3,7 @@ package it.danieleverducci.lunatracker.repository import android.content.Context import android.content.Context.MODE_PRIVATE import android.content.SharedPreferences +import androidx.core.content.edit class LocalSettingsRepository(val context: Context) { companion object { @@ -12,6 +13,7 @@ class LocalSettingsRepository(val context: Context) { 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 @@ -28,6 +30,14 @@ class LocalSettingsRepository(val context: Context) { return sharedPreferences.getInt(SHARED_PREFS_BB_CONTENT, 1) } + fun saveNoBreastfeeding(content: Boolean) { + sharedPreferences.edit().putBoolean(SHARED_PREFS_NO_BREASTFEEDING, content).apply() + } + + fun loadNoBreastfeeding(): Boolean { + return sharedPreferences.getBoolean(SHARED_PREFS_NO_BREASTFEEDING, false) + } + fun saveDataRepository(repo: DATA_REPO) { val spe = sharedPreferences.edit() spe.putString( diff --git a/app/src/main/java/utils/DateUtils.kt b/app/src/main/java/utils/DateUtils.kt index b192f25..25803da 100644 --- a/app/src/main/java/utils/DateUtils.kt +++ b/app/src/main/java/utils/DateUtils.kt @@ -7,6 +7,62 @@ import java.util.Date class DateUtils { companion object { + 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) @@ -25,10 +81,10 @@ class DateUtils { return DateFormat.getDateFormat(context).format(Date(unixTime*1000)) + "\n" + DateFormat.getTimeFormat(context).format(Date(unixTime*1000)) - var formattedTime = StringBuilder() + val formattedTime = StringBuilder() if (hoursAgo > 0) { formattedTime.append(hoursAgo).append(" ") - if (hoursAgo.toInt() == 1) + if (hoursAgo == 1) formattedTime.append(context.getString(R.string.hour_ago)) else formattedTime.append(context.getString(R.string.hours_ago)) @@ -37,7 +93,7 @@ class DateUtils { if (formattedTime.isNotEmpty()) formattedTime.append(", ") formattedTime.append(minutesAgo).append(" ") - if (minutesAgo.toInt() == 1) + if (minutesAgo == 1) formattedTime.append(context.getString(R.string.minute_ago)) else formattedTime.append(context.getString(R.string.minutes_ago)) diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 125caf0..a04a510 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -30,7 +30,7 @@ android:layout_height="wrap_content" android:layout_weight="1" android:text="@string/title" - android:textSize="26dp" + android:textSize="26sp" android:gravity="center"/> @@ -68,23 +68,17 @@ android:id="@+id/logbooks_add_button" android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_marginLeft="10dp" + android:layout_marginStart="10dp" android:paddingLeft="16dp" android:paddingRight="16dp" android:textStyle="bold" android:textColor="@color/accent" - android:textSize="20dp" + android:textSize="20sp" android:text="+" android:background="@drawable/button_background"/> - - + android:layout_height="wrap_content" + android:id="@+id/layout_nipples"> @@ -167,29 +162,29 @@ android:id="@+id/button_change_poo" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_margin="10dp" + android:layout_margin="5dp" android:layout_weight="2" android:background="@drawable/button_background" android:gravity="center_horizontal" - android:textSize="30dp" + android:textSize="30sp" android:text="🚼 💩"/> - + android:layout_marginTop="5dp" + android:orientation="horizontal"> - + + + + + @@ -264,7 +269,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" - android:drawableLeft="@drawable/ic_sync" + android:drawableStart="@drawable/ic_sync" android:drawableTint="@color/white" android:drawablePadding="10dp" android:text="@string/no_connection_retry" @@ -275,7 +280,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" - android:drawableLeft="@drawable/ic_settings" + android:drawableStart="@drawable/ic_settings" android:drawableTint="@color/white" android:drawablePadding="10dp" android:text="@string/no_connection_go_to_settings" diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index 0aace63..76f5904 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -39,26 +39,26 @@ @@ -66,7 +66,7 @@ android:id="@+id/settings_data_webdav_url" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="30dp" + android:layout_marginStart="30dp" android:hint="@string/settings_storage_dav_url_hint" android:inputType="textUri" android:background="@drawable/textview_background"/> @@ -75,7 +75,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:layout_marginLeft="30dp" + android:layout_marginStart="30dp" android:textStyle="bold" android:text="@string/settings_storage_dav_user"/> @@ -83,7 +83,7 @@ android:id="@+id/settings_data_webdav_user" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginLeft="30dp" + android:layout_marginStart="30dp" android:inputType="textEmailAddress" android:background="@drawable/textview_background"/> @@ -91,17 +91,23 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" - android:layout_marginLeft="30dp" + android:layout_marginStart="30dp" android:textStyle="bold" android:text="@string/settings_storage_dav_pass"/> - + android:layout_marginStart="30dp" + app:passwordToggleEnabled="true"> + + + - - - -