Add sleep tracking, statistics module and backup features
Features: - Sleep tracking with timer and manual duration input - Statistics module with 5 tabs (daily summary, feeding, diapers, sleep, growth) - Export/Import backup functionality in settings - Complete German, French and Italian translations
This commit is contained in:
@@ -0,0 +1,207 @@
|
||||
package it.danieleverducci.lunatracker
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.Spinner
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.viewpager2.adapter.FragmentStateAdapter
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.google.android.material.tabs.TabLayout
|
||||
import com.google.android.material.tabs.TabLayoutMediator
|
||||
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import it.danieleverducci.lunatracker.fragments.DailySummaryFragment
|
||||
import it.danieleverducci.lunatracker.fragments.DiaperStatsFragment
|
||||
import it.danieleverducci.lunatracker.fragments.FeedingStatsFragment
|
||||
import it.danieleverducci.lunatracker.fragments.GrowthStatsFragment
|
||||
import it.danieleverducci.lunatracker.fragments.SleepStatsFragment
|
||||
import it.danieleverducci.lunatracker.repository.FileLogbookRepository
|
||||
import it.danieleverducci.lunatracker.repository.LocalSettingsRepository
|
||||
import it.danieleverducci.lunatracker.repository.LogbookLoadedListener
|
||||
import it.danieleverducci.lunatracker.repository.LogbookRepository
|
||||
import it.danieleverducci.lunatracker.repository.WebDAVLogbookRepository
|
||||
import okio.IOException
|
||||
import org.json.JSONException
|
||||
import utils.StatisticsCalculator
|
||||
|
||||
class StatisticsActivity : AppCompatActivity() {
|
||||
|
||||
companion object {
|
||||
const val TAG = "StatisticsActivity"
|
||||
const val EXTRA_LOGBOOK_NAME = "logbook_name"
|
||||
}
|
||||
|
||||
private lateinit var viewPager: ViewPager2
|
||||
private lateinit var tabLayout: TabLayout
|
||||
private lateinit var periodSpinner: Spinner
|
||||
|
||||
private var events: List<LunaEvent> = emptyList()
|
||||
private var selectedPeriod: Int = 7 // Default 7 days
|
||||
private var logbookName: String = ""
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_statistics)
|
||||
|
||||
// Back button
|
||||
findViewById<ImageView>(R.id.button_back).setOnClickListener {
|
||||
finish()
|
||||
}
|
||||
|
||||
// Title with logbook name
|
||||
logbookName = intent.getStringExtra(EXTRA_LOGBOOK_NAME) ?: ""
|
||||
if (logbookName.isNotEmpty()) {
|
||||
findViewById<TextView>(R.id.statistics_title).text =
|
||||
"${getString(R.string.statistics_title)} - $logbookName"
|
||||
}
|
||||
|
||||
// Period spinner
|
||||
periodSpinner = findViewById(R.id.period_spinner)
|
||||
val periods = arrayOf(
|
||||
getString(R.string.stats_period_7days),
|
||||
getString(R.string.stats_period_14days),
|
||||
getString(R.string.stats_period_30days)
|
||||
)
|
||||
val periodAdapter = ArrayAdapter(this, android.R.layout.simple_spinner_item, periods)
|
||||
periodAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
periodSpinner.adapter = periodAdapter
|
||||
periodSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
selectedPeriod = when (position) {
|
||||
0 -> 7
|
||||
1 -> 14
|
||||
2 -> 30
|
||||
else -> 7
|
||||
}
|
||||
notifyFragmentsOfPeriodChange()
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
}
|
||||
|
||||
// ViewPager and TabLayout
|
||||
viewPager = findViewById(R.id.view_pager)
|
||||
tabLayout = findViewById(R.id.tab_layout)
|
||||
|
||||
// Load events
|
||||
loadEvents()
|
||||
}
|
||||
|
||||
private fun loadEvents() {
|
||||
val settingsRepo = LocalSettingsRepository(this)
|
||||
val repository: LogbookRepository = when (settingsRepo.loadDataRepository()) {
|
||||
LocalSettingsRepository.DATA_REPO.WEBDAV -> {
|
||||
val credentials = settingsRepo.loadWebdavCredentials()
|
||||
if (credentials != null) {
|
||||
WebDAVLogbookRepository(credentials[0], credentials[1], credentials[2])
|
||||
} else {
|
||||
FileLogbookRepository()
|
||||
}
|
||||
}
|
||||
LocalSettingsRepository.DATA_REPO.LOCAL_FILE -> FileLogbookRepository()
|
||||
}
|
||||
|
||||
repository.loadLogbook(this, logbookName, object : LogbookLoadedListener {
|
||||
override fun onLogbookLoaded(logbook: Logbook) {
|
||||
runOnUiThread {
|
||||
events = logbook.logs
|
||||
setupViewPager()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIOError(error: IOException) {
|
||||
Log.e(TAG, "IO error loading logbook", error)
|
||||
runOnUiThread {
|
||||
events = emptyList()
|
||||
setupViewPager()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onWebDAVError(error: SardineException) {
|
||||
Log.e(TAG, "WebDAV error loading logbook", error)
|
||||
runOnUiThread {
|
||||
events = emptyList()
|
||||
setupViewPager()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onJSONError(error: JSONException) {
|
||||
Log.e(TAG, "JSON error loading logbook", error)
|
||||
runOnUiThread {
|
||||
events = emptyList()
|
||||
setupViewPager()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(error: Exception) {
|
||||
Log.e(TAG, "Error loading logbook", error)
|
||||
runOnUiThread {
|
||||
events = emptyList()
|
||||
setupViewPager()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun setupViewPager() {
|
||||
val adapter = StatisticsPagerAdapter(this)
|
||||
viewPager.adapter = adapter
|
||||
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
tab.text = when (position) {
|
||||
0 -> getString(R.string.stats_tab_today)
|
||||
1 -> getString(R.string.stats_tab_feeding)
|
||||
2 -> getString(R.string.stats_tab_diapers)
|
||||
3 -> getString(R.string.stats_tab_sleep)
|
||||
4 -> getString(R.string.stats_tab_growth)
|
||||
else -> ""
|
||||
}
|
||||
}.attach()
|
||||
}
|
||||
|
||||
private fun notifyFragmentsOfPeriodChange() {
|
||||
if (events.isEmpty()) return
|
||||
|
||||
// Force fragment refresh by recreating adapter
|
||||
val currentItem = viewPager.currentItem
|
||||
viewPager.adapter = StatisticsPagerAdapter(this)
|
||||
viewPager.setCurrentItem(currentItem, false)
|
||||
TabLayoutMediator(tabLayout, viewPager) { tab, position ->
|
||||
tab.text = when (position) {
|
||||
0 -> getString(R.string.stats_tab_today)
|
||||
1 -> getString(R.string.stats_tab_feeding)
|
||||
2 -> getString(R.string.stats_tab_diapers)
|
||||
3 -> getString(R.string.stats_tab_sleep)
|
||||
4 -> getString(R.string.stats_tab_growth)
|
||||
else -> ""
|
||||
}
|
||||
}.attach()
|
||||
}
|
||||
|
||||
fun getEvents(): List<LunaEvent> = events
|
||||
fun getSelectedPeriod(): Int = selectedPeriod
|
||||
fun getCalculator(): StatisticsCalculator = StatisticsCalculator(events)
|
||||
|
||||
private inner class StatisticsPagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
|
||||
override fun getItemCount(): Int = 5
|
||||
|
||||
override fun createFragment(position: Int): Fragment {
|
||||
return when (position) {
|
||||
0 -> DailySummaryFragment()
|
||||
1 -> FeedingStatsFragment()
|
||||
2 -> DiaperStatsFragment()
|
||||
3 -> SleepStatsFragment()
|
||||
4 -> GrowthStatsFragment()
|
||||
else -> DailySummaryFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user