forked from penguin86/luna-tracker
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:
@@ -1,20 +1,26 @@
|
||||
package it.danieleverducci.lunatracker
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.widget.EditText
|
||||
import android.widget.RadioButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
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.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.WebDAVLogbookRepository
|
||||
import okio.IOException
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
|
||||
open class SettingsActivity : AppCompatActivity() {
|
||||
protected lateinit var settingsRepository: LocalSettingsRepository
|
||||
@@ -27,6 +33,15 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
protected lateinit var switchNoBreastfeeding: SwitchMaterial
|
||||
protected lateinit var textViewSignature: EditText
|
||||
|
||||
// Activity Result Launchers for Export/Import
|
||||
private val exportLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.CreateDocument("application/json")
|
||||
) { uri -> uri?.let { exportLogbookToUri(it) } }
|
||||
|
||||
private val importLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.OpenDocument()
|
||||
) { uri -> uri?.let { importLogbookFromUri(it) } }
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
@@ -46,6 +61,12 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
findViewById<View>(R.id.settings_cancel).setOnClickListener({
|
||||
finish()
|
||||
})
|
||||
findViewById<View>(R.id.settings_export).setOnClickListener({
|
||||
startExport()
|
||||
})
|
||||
findViewById<View>(R.id.settings_import).setOnClickListener({
|
||||
startImport()
|
||||
})
|
||||
|
||||
settingsRepository = LocalSettingsRepository(this)
|
||||
loadSettings()
|
||||
@@ -198,4 +219,136 @@ open class SettingsActivity : AppCompatActivity() {
|
||||
fun onCopyLocalLogbooksToWebdavFinished(errors: String?)
|
||||
}
|
||||
|
||||
// Export/Import functionality
|
||||
private fun startExport() {
|
||||
val timestamp = java.text.SimpleDateFormat("yyyyMMdd_HHmmss", java.util.Locale.US)
|
||||
.format(java.util.Date())
|
||||
exportLauncher.launch("lunatracker_backup_$timestamp.json")
|
||||
}
|
||||
|
||||
private fun startImport() {
|
||||
importLauncher.launch(arrayOf("application/json"))
|
||||
}
|
||||
|
||||
private fun exportLogbookToUri(uri: Uri) {
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
Thread {
|
||||
try {
|
||||
val fileLogbookRepo = FileLogbookRepository()
|
||||
val logbooks = fileLogbookRepo.getAllLogbooks(this)
|
||||
|
||||
val json = JSONObject().apply {
|
||||
put("version", 1)
|
||||
put("app", "LunaTracker")
|
||||
put("exported_at", System.currentTimeMillis())
|
||||
put("logbooks", JSONArray().apply {
|
||||
logbooks.forEach { logbook ->
|
||||
put(JSONObject().apply {
|
||||
put("name", logbook.name)
|
||||
put("events", JSONArray().apply {
|
||||
logbook.logs.forEach { event ->
|
||||
put(event.toJson())
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
contentResolver.openOutputStream(uri)?.use { outputStream ->
|
||||
outputStream.write(json.toString(2).toByteArray(Charsets.UTF_8))
|
||||
}
|
||||
|
||||
val eventCount = logbooks.sumOf { it.logs.size }
|
||||
runOnUiThread {
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.export_success, eventCount),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.export_error) + e.message,
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun importLogbookFromUri(uri: Uri) {
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
Thread {
|
||||
try {
|
||||
val jsonString = contentResolver.openInputStream(uri)?.bufferedReader()?.readText()
|
||||
?: throw Exception("Could not read file")
|
||||
|
||||
val json = JSONObject(jsonString)
|
||||
val version = json.optInt("version", 1)
|
||||
|
||||
val fileLogbookRepo = FileLogbookRepository()
|
||||
var totalEvents = 0
|
||||
|
||||
if (json.has("logbooks")) {
|
||||
// New format with multiple logbooks
|
||||
val logbooksArray = json.getJSONArray("logbooks")
|
||||
for (i in 0 until logbooksArray.length()) {
|
||||
val logbookJson = logbooksArray.getJSONObject(i)
|
||||
val name = logbookJson.optString("name", "")
|
||||
val eventsArray = logbookJson.getJSONArray("events")
|
||||
|
||||
val logbook = Logbook(name)
|
||||
for (j in 0 until eventsArray.length()) {
|
||||
try {
|
||||
logbook.logs.add(LunaEvent(eventsArray.getJSONObject(j)))
|
||||
totalEvents++
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Skip invalid events
|
||||
}
|
||||
}
|
||||
fileLogbookRepo.saveLogbook(this, logbook)
|
||||
}
|
||||
} else if (json.has("events")) {
|
||||
// Old format with single logbook
|
||||
val name = json.optString("logbook_name", "")
|
||||
val eventsArray = json.getJSONArray("events")
|
||||
|
||||
val logbook = Logbook(name)
|
||||
for (i in 0 until eventsArray.length()) {
|
||||
try {
|
||||
logbook.logs.add(LunaEvent(eventsArray.getJSONObject(i)))
|
||||
totalEvents++
|
||||
} catch (e: IllegalArgumentException) {
|
||||
// Skip invalid events
|
||||
}
|
||||
}
|
||||
fileLogbookRepo.saveLogbook(this, logbook)
|
||||
}
|
||||
|
||||
runOnUiThread {
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.import_success, totalEvents),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
runOnUiThread {
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Toast.makeText(
|
||||
this,
|
||||
getString(R.string.import_error) + ": " + e.message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user