package it.danieleverducci.lunatracker import android.app.DatePickerDialog import android.app.TimePickerDialog import android.content.DialogInterface import android.content.Intent import android.os.Bundle import android.os.Handler import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.AdapterView import android.widget.ArrayAdapter import android.widget.Button import android.widget.EditText import android.widget.NumberPicker import android.widget.PopupWindow import android.widget.Spinner import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.progressindicator.LinearProgressIndicator import com.google.android.material.slider.Slider import com.thegrizzlylabs.sardineandroid.impl.SardineException import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter import it.danieleverducci.lunatracker.entities.Logbook import it.danieleverducci.lunatracker.entities.LunaEvent import it.danieleverducci.lunatracker.repository.FileLogbookRepository import it.danieleverducci.lunatracker.repository.LocalSettingsRepository import it.danieleverducci.lunatracker.repository.LogbookListObtainedListener import it.danieleverducci.lunatracker.repository.LogbookLoadedListener import it.danieleverducci.lunatracker.repository.LogbookRepository import it.danieleverducci.lunatracker.repository.LogbookSavedListener 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.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 } var logbook: Logbook? = null var pauseLogbookUpdate = false lateinit var progressIndicator: LinearProgressIndicator 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) loadLogbook(logbook!!.name) handler.postDelayed(updateListRunnable, 1000 * 60) } var logbookRepo: LogbookRepository? = null var showingOverflowPopupWindow = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) handler = Handler(mainLooper) // 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) recyclerView.setLayoutManager(LinearLayoutManager(applicationContext)) // Set listeners findViewById(R.id.logbooks_add_button).setOnClickListener { showAddLogbookDialog(true) } findViewById(R.id.button_bottle).setOnClickListener { addBabyBottleEvent(LunaEvent(LunaEvent.TYPE_BABY_BOTTLE)) } findViewById(R.id.button_food).setOnClickListener { addNoteEvent(LunaEvent(LunaEvent.TYPE_FOOD)) } findViewById(R.id.button_nipple_left).setOnClickListener { addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_LEFT_NIPPLE)) } findViewById(R.id.button_nipple_both).setOnClickListener { addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_BOTH_NIPPLE)) } findViewById(R.id.button_nipple_right).setOnClickListener { addPlainEvent(LunaEvent(LunaEvent.TYPE_BREASTFEEDING_RIGHT_NIPPLE)) } findViewById(R.id.button_change_poo).setOnClickListener { addAmountEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_POO)) } findViewById(R.id.button_change_pee).setOnClickListener { addAmountEvent(LunaEvent(LunaEvent.TYPE_DIAPERCHANGE_PEE)) } val moreButton = findViewById(R.id.button_more) moreButton.setOnClickListener { showOverflowPopupWindow(moreButton) } findViewById(R.id.button_no_connection_settings).setOnClickListener { showSettings() } findViewById(R.id.button_settings).setOnClickListener { showSettings() } findViewById(R.id.button_no_connection_retry).setOnClickListener { // This may happen at start, when logbook is still null: better ask the logbook list loadLogbookList() } findViewById(R.id.button_sync).setOnClickListener { loadLogbookList() } } private fun setListAdapter(items: ArrayList) { val adapter = LunaEventRecyclerAdapter(this, items) adapter.onItemClickListener = object : LunaEventRecyclerAdapter.OnItemClickListener { override fun onItemClick(event: LunaEvent) { showEventDetailDialog(event) } } recyclerView.adapter = adapter } fun showSettings() { val i = Intent(this, SettingsActivity::class.java) startActivity(i) } fun showLogbook() { // Show logbook if (logbook == null) Log.w(TAG, "showLogbook(): logbook is null!") setListAdapter(logbook?.logs ?: arrayListOf()) } override fun onStart() { super.onStart() val settingsRepository = LocalSettingsRepository(this) if (settingsRepository.loadDataRepository() == LocalSettingsRepository.DATA_REPO.WEBDAV) { val webDavCredentials = settingsRepository.loadWebdavCredentials() if (webDavCredentials == null) { throw IllegalStateException("Corrupted local settings: repo is webdav, but no webdav login data saved") } logbookRepo = WebDAVLogbookRepository( webDavCredentials[0], webDavCredentials[1], webDavCredentials[2] ) } else { logbookRepo = FileLogbookRepository() } signature = settingsRepository.loadSignature() val noBreastfeeding = settingsRepository.loadNoBreastfeeding() findViewById(R.id.layout_nipples).visibility = when (noBreastfeeding) { true -> View.GONE false -> View.VISIBLE } // Update list dates recyclerView.adapter?.notifyDataSetChanged() if (logbook != null) { // Already running: reload data for currently selected logbook loadLogbook(logbook!!.name) } else { // First start: load logbook list loadLogbookList() } } override fun onStop() { handler.removeCallbacks(updateListRunnable) super.onStop() } fun getAllEvents(): ArrayList { return logbook?.logs ?: arrayListOf() } fun addBabyBottleEvent(event: LunaEvent) { setToPreviousQuantity(event) askBabyBottleContent(event, true) { saveEvent(event) } } fun askBabyBottleContent(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { val d = AlertDialog.Builder(this) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_bottle, null) d.setTitle(event.getTypeDescription(this)) d.setMessage(event.getDialogMessage(this)) d.setView(dialogView) val numberPicker = dialogView.findViewById(R.id.dialog_number_picker) numberPicker.minValue = 1 // "10" numberPicker.maxValue = 25 // "250 numberPicker.displayedValues = ((10..250 step 10).map { it.toString() }.toTypedArray()) numberPicker.wrapSelectorWheel = false numberPicker.value = event.quantity / 10 val dateTV = dialogView.findViewById(R.id.dialog_date_picker) val pickedTime = datePickerHelper(event.time, dateTV) if (!showTime) { dateTV.visibility = View.GONE } d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> event.time = pickedTime.time.time / 1000 event.quantity = numberPicker.value * 10 onPositive() dialogInterface.dismiss() } d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() } val alertDialog = d.create() alertDialog.show() } fun addWeightEvent(event: LunaEvent) { setToPreviousQuantity(event) askWeightValue(event, true) { saveEvent(event) } } fun askWeightValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { // Show number picker dialog val d = AlertDialog.Builder(this) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_weight, null) d.setTitle(event.getTypeDescription(this)) d.setMessage(event.getDialogMessage(this)) d.setView(dialogView) val weightET = dialogView.findViewById(R.id.dialog_number_edittext) weightET.setText(event.quantity.toString()) val dateTV = dialogView.findViewById(R.id.dialog_date_picker) val pickedTime = datePickerHelper(event.time, dateTV) if (!showTime) { dateTV.visibility = View.GONE } d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> val weight = weightET.text.toString().toIntOrNull() if (weight != null) { event.time = pickedTime.time.time / 1000 event.quantity = weight onPositive() } else { Toast.makeText(this, R.string.toast_integer_error, Toast.LENGTH_SHORT).show() } dialogInterface.dismiss() } d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() } val alertDialog = d.create() alertDialog.show() } fun addTemperatureEvent(event: LunaEvent) { setToPreviousQuantity(event) askTemperatureValue(event, true) { saveEvent(event) } } fun askTemperatureValue(event: LunaEvent, showTime: Boolean, onPositive: () -> Unit) { // Show number picker dialog val d = AlertDialog.Builder(this) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_temperature, null) d.setTitle(event.getTypeDescription(this)) d.setMessage(event.getDialogMessage(this)) d.setView(dialogView) val tempSlider = dialogView.findViewById(R.id.dialog_temperature_value) val range = NumericUtils(this).getValidEventQuantityRange(LunaEvent.TYPE_TEMPERATURE)!! tempSlider.valueFrom = range.first.toFloat() tempSlider.valueTo = range.second.toFloat() tempSlider.value = if (event.quantity == 0) { range.third.toFloat() // default } else { event.quantity.toFloat() / 10 } val dateTV = dialogView.findViewById(R.id.dialog_date_picker) val pickedTime = datePickerHelper(event.time, dateTV) if (!showTime) { dateTV.visibility = View.GONE } val tempTextView = dialogView.findViewById(R.id.dialog_temperature_display) tempTextView.text = tempSlider.value.toString() tempSlider.addOnChangeListener({ s, v, b -> tempTextView.text = v.toString() }) d.setPositiveButton(android.R.string.ok) { dialogInterface, i -> event.time = pickedTime.time.time / 1000 event.quantity = (tempSlider.value * 10).toInt() // temperature in tenth of a grade onPositive() dialogInterface.dismiss() } d.setNegativeButton(android.R.string.cancel) { dialogInterface, i -> dialogInterface.dismiss() } val alertDialog = d.create() alertDialog.show() } fun datePickerHelper(time: Long, dateTextView: TextView, onChange: (Long) -> Unit = {}): Calendar { dateTextView.text = DateUtils.formatDateTime(time) val dateTime = Calendar.getInstance() dateTime.time = Date(time * 1000) dateTextView.setOnClickListener { // Show datetime picker val startYear = dateTime.get(Calendar.YEAR) val startMonth = dateTime.get(Calendar.MONTH) val startDay = dateTime.get(Calendar.DAY_OF_MONTH) val startHour = dateTime.get(Calendar.HOUR_OF_DAY) val startMinute = dateTime.get(Calendar.MINUTE) DatePickerDialog(this, { _, year, month, day -> TimePickerDialog( this, { _, hour, minute -> dateTime.set(year, month, day, hour, minute) dateTextView.text = DateUtils.formatDateTime(dateTime.time.time / 1000) onChange.invoke(dateTime.time.time / 1000) }, startHour, startMinute, android.text.format.DateFormat.is24HourFormat(this@MainActivity) ).show() }, startYear, startMonth, startDay).show() } return dateTime } fun saveEvent(event: LunaEvent) { if (!getAllEvents().contains(event)) { // new event logEvent(event) } logbook?.sort() recyclerView.adapter?.notifyDataSetChanged() saveLogbook() } fun addSleepEvent(event: LunaEvent) { askSleepValue(event) { saveEvent(event) } } fun askSleepValue(event: LunaEvent, onPositive: () -> Unit) { val d = AlertDialog.Builder(this) val dialogView = layoutInflater.inflate(R.layout.dialog_edit_duration, null) d.setTitle(event.getTypeDescription(this)) d.setMessage(event.getDialogMessage(this)) d.setView(dialogView) val durationTextView = dialogView.findViewById(R.id.dialog_date_duration) val durationNowButton = dialogView.findViewById