First working sync (with hardcoded credentials and no concurrent modification checks)
This commit is contained in:
parent
106f721365
commit
6432045419
3
.gitignore
vendored
3
.gitignore
vendored
@ -103,3 +103,6 @@ app/release/output-metadata.json
|
||||
.idea/misc.xml
|
||||
.idea/deploymentTargetSelector.xml
|
||||
.idea/other.xml
|
||||
|
||||
# Other
|
||||
app/src/main/java/it/danieleverducci/lunatracker/TemporaryHardcodedCredentials.kt
|
||||
|
3
.idea/.gitignore
vendored
3
.idea/.gitignore
vendored
@ -1,3 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="21" />
|
||||
</component>
|
||||
</project>
|
@ -1,57 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="ComposePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="ComposePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="GlancePreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewDeviceShouldUseNewSpec" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||
<option name="composableFile" value="true" />
|
||||
<option name="previewFile" value="true" />
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
@ -51,6 +51,8 @@ dependencies {
|
||||
implementation(libs.androidx.material3)
|
||||
implementation(libs.androidx.appcompat)
|
||||
implementation(libs.androidx.recyclerview)
|
||||
implementation("com.github.thegrizzlylabs:sardine-android:v0.9")
|
||||
implementation(libs.material)
|
||||
testImplementation(libs.junit)
|
||||
androidTestImplementation(libs.androidx.junit)
|
||||
androidTestImplementation(libs.androidx.espresso.core)
|
||||
|
@ -2,6 +2,8 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||
|
@ -1,22 +1,25 @@
|
||||
package it.danieleverducci.lunatracker
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.preference.PreferenceManager
|
||||
import android.util.Log
|
||||
import android.view.View
|
||||
import android.widget.NumberPicker
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
import it.danieleverducci.lunatracker.adapters.LunaEventRecyclerAdapter
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import it.danieleverducci.lunatracker.entities.LunaEventType
|
||||
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
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
@ -28,28 +31,30 @@ class MainActivity : AppCompatActivity() {
|
||||
|
||||
lateinit var logbook: Logbook
|
||||
lateinit var adapter: LunaEventRecyclerAdapter
|
||||
lateinit var progressIndicator: LinearProgressIndicator
|
||||
lateinit var recyclerView: RecyclerView
|
||||
lateinit var handler: Handler
|
||||
val updateListRunnable: Runnable = Runnable {
|
||||
adapter.notifyDataSetChanged()
|
||||
handler.postDelayed(updateListRunnable, 1000*30)
|
||||
loadLogbook()
|
||||
handler.postDelayed(updateListRunnable, 1000*60)
|
||||
}
|
||||
val logbookRepo: LogbookRepository = WebDAVLogbookRepository( // TODO: support also FileLogbookRepository
|
||||
TemporaryHardcodedCredentials.URL,
|
||||
TemporaryHardcodedCredentials.USERNAME,
|
||||
TemporaryHardcodedCredentials.PASSWORD
|
||||
)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
handler = Handler(mainLooper)
|
||||
|
||||
// Load data
|
||||
logbook = Logbook.load(this)
|
||||
adapter = LunaEventRecyclerAdapter(this)
|
||||
|
||||
// Show view
|
||||
setContentView(R.layout.activity_main)
|
||||
|
||||
// Show logbook
|
||||
progressIndicator = findViewById<LinearProgressIndicator>(R.id.progress_indicator)
|
||||
recyclerView = findViewById<RecyclerView>(R.id.list_events)
|
||||
recyclerView.setLayoutManager(LinearLayoutManager(this))
|
||||
adapter = LunaEventRecyclerAdapter(this)
|
||||
adapter.items.addAll(logbook.logs)
|
||||
recyclerView.setLayoutManager(LinearLayoutManager(applicationContext))
|
||||
recyclerView.adapter = adapter
|
||||
|
||||
// Set listeners
|
||||
@ -82,11 +87,18 @@ class MainActivity : AppCompatActivity() {
|
||||
) }
|
||||
}
|
||||
|
||||
fun showLogbook() {
|
||||
// Show logbook
|
||||
adapter.items.clear()
|
||||
adapter.items.addAll(logbook.logs)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
|
||||
// Update list dates
|
||||
adapter.notifyDataSetChanged()
|
||||
loadLogbook()
|
||||
handler.postDelayed(updateListRunnable, 1000*30)
|
||||
}
|
||||
|
||||
@ -138,13 +150,52 @@ class MainActivity : AppCompatActivity() {
|
||||
alertDialog.show()
|
||||
}
|
||||
|
||||
fun loadLogbook() {
|
||||
// Load data
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
logbookRepo.loadLogbook(this, object: LogbookLoadedListener{
|
||||
override fun onLogbookLoaded(lb: Logbook) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
logbook = lb
|
||||
showLogbook()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(error: String) {
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
Log.e(TAG, "Unable to load logbook. Create a new one.")
|
||||
logbook = Logbook()
|
||||
showLogbook()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun logEvent(event: LunaEvent) {
|
||||
adapter.items.add(0, event)
|
||||
adapter.notifyItemInserted(0)
|
||||
recyclerView.smoothScrollToPosition(0)
|
||||
|
||||
progressIndicator.visibility = View.VISIBLE
|
||||
logbook.logs.add(0, event)
|
||||
logbook.save(this)
|
||||
logbookRepo.saveLogbook(this, logbook, object: LogbookSavedListener{
|
||||
override fun onLogbookSaved() {
|
||||
Log.d(TAG, "Logbook saved")
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(error: String) {
|
||||
Log.e(TAG, "ERROR: Logbook was NOT saved!")
|
||||
runOnUiThread({
|
||||
progressIndicator.visibility = View.INVISIBLE
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
Toast.makeText(this, R.string.toast_event_added, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
@ -1,42 +1,5 @@
|
||||
package it.danieleverducci.lunatracker.entities
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class Logbook {
|
||||
companion object {
|
||||
val TAG = "Logbook"
|
||||
|
||||
fun load(context: Context): Logbook {
|
||||
val logbook = Logbook()
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
try {
|
||||
val json = FileInputStream(file).bufferedReader().use { it.readText() }
|
||||
val ja = JSONArray(json)
|
||||
for (i in 0 until ja.length()) {
|
||||
val jo = ja.getJSONObject(i)
|
||||
val evt = LunaEvent.fromJson(jo)
|
||||
logbook.logs.add(evt)
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.d(TAG, "No logbook file found")
|
||||
}
|
||||
return logbook
|
||||
}
|
||||
}
|
||||
|
||||
val logs = ArrayList<LunaEvent>()
|
||||
|
||||
fun save(context: Context) {
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
val ja = JSONArray()
|
||||
for (l in logs) {
|
||||
ja.put(l.toJson())
|
||||
}
|
||||
file.writeText(ja.toString())
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package it.danieleverducci.lunatracker.repository
|
||||
|
||||
import android.content.Context
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
import android.util.Log
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import org.json.JSONArray
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
class FileLogbookRepository: LogbookRepository {
|
||||
companion object {
|
||||
val TAG = "FileLogbookRepository"
|
||||
}
|
||||
|
||||
override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
|
||||
val logbook = Logbook()
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
try {
|
||||
val json = FileInputStream(file).bufferedReader().use { it.readText() }
|
||||
val ja = JSONArray(json)
|
||||
for (i in 0 until ja.length()) {
|
||||
val jo = ja.getJSONObject(i)
|
||||
val evt = LunaEvent.fromJson(jo)
|
||||
logbook.logs.add(evt)
|
||||
}
|
||||
} catch (e: FileNotFoundException) {
|
||||
Log.d(TAG, "No logbook file found")
|
||||
listener.onError(e.toString())
|
||||
}
|
||||
listener.onLogbookLoaded(logbook)
|
||||
}
|
||||
|
||||
override fun saveLogbook(
|
||||
context: Context,
|
||||
logbook: Logbook,
|
||||
listener: LogbookSavedListener
|
||||
) {
|
||||
val file = File(context.getFilesDir(), "data.json")
|
||||
val ja = JSONArray()
|
||||
for (l in logbook.logs) {
|
||||
ja.put(l.toJson())
|
||||
}
|
||||
file.writeText(ja.toString())
|
||||
listener.onLogbookSaved()
|
||||
}
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package it.danieleverducci.lunatracker.repository
|
||||
|
||||
import android.content.Context
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
|
||||
interface LogbookRepository {
|
||||
fun loadLogbook(context: Context, listener: LogbookLoadedListener)
|
||||
fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener)
|
||||
}
|
||||
|
||||
interface LogbookLoadedListener {
|
||||
fun onLogbookLoaded(logbook: Logbook)
|
||||
fun onError(error: String)
|
||||
}
|
||||
|
||||
interface LogbookSavedListener {
|
||||
fun onLogbookSaved()
|
||||
fun onError(error: String)
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
package it.danieleverducci.lunatracker.repository
|
||||
|
||||
import android.content.Context
|
||||
import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
|
||||
import com.thegrizzlylabs.sardineandroid.impl.SardineException
|
||||
import it.danieleverducci.lunatracker.TemporaryHardcodedCredentials
|
||||
import it.danieleverducci.lunatracker.entities.Logbook
|
||||
import it.danieleverducci.lunatracker.entities.LunaEvent
|
||||
import kotlinx.coroutines.Runnable
|
||||
import org.json.JSONArray
|
||||
import java.io.BufferedReader
|
||||
import kotlin.io.bufferedReader
|
||||
|
||||
class WebDAVLogbookRepository(val webDavURL: String, val username: String, val password: String): LogbookRepository {
|
||||
companion object {
|
||||
val TAG = "LogbookRepository"
|
||||
val FILE_NAME = "lunatracker_logbook.json"
|
||||
}
|
||||
val sardine: OkHttpSardine = OkHttpSardine()
|
||||
|
||||
init {
|
||||
sardine.setCredentials(
|
||||
username,
|
||||
password
|
||||
)
|
||||
}
|
||||
|
||||
override fun loadLogbook(context: Context, listener: LogbookLoadedListener) {
|
||||
Thread(Runnable {
|
||||
try {
|
||||
val inputStream = sardine.get("$webDavURL/$FILE_NAME")
|
||||
val json = inputStream.bufferedReader().use(BufferedReader::readText)
|
||||
val ja = JSONArray(json)
|
||||
val logbook = Logbook()
|
||||
for (i in 0 until ja.length()) {
|
||||
val jo = ja.getJSONObject(i)
|
||||
val evt = LunaEvent.fromJson(jo)
|
||||
logbook.logs.add(evt)
|
||||
}
|
||||
listener.onLogbookLoaded(logbook)
|
||||
} catch (e: SardineException) {
|
||||
listener.onError(e.toString())
|
||||
}
|
||||
}).start()
|
||||
}
|
||||
|
||||
override fun saveLogbook(context: Context, logbook: Logbook, listener: LogbookSavedListener) {
|
||||
Thread(Runnable {
|
||||
// Lock logbook on WebDAV to avoid concurrent changes
|
||||
//sardine.lock(getUrl())
|
||||
// Reload logbook from WebDAV
|
||||
// Merge logbooks (based on time)
|
||||
// Write logbook
|
||||
// Unlock logbook on WebDAV
|
||||
//sardine.unlock(getUrl())
|
||||
|
||||
val ja = JSONArray()
|
||||
for (l in logbook.logs) {
|
||||
ja.put(l.toJson())
|
||||
}
|
||||
try {
|
||||
sardine.put(getUrl(), ja.toString().toByteArray())
|
||||
listener.onLogbookSaved()
|
||||
} catch (e: SardineException) {
|
||||
listener.onError(e.toString())
|
||||
}
|
||||
|
||||
}).start()
|
||||
}
|
||||
|
||||
private fun getUrl(): String {
|
||||
return "${TemporaryHardcodedCredentials.URL}/$FILE_NAME"
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="30dp"
|
||||
android:paddingLeft="15dp"
|
||||
@ -116,11 +116,21 @@
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/progress_indicator"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="10dp"
|
||||
android:indeterminate="true"
|
||||
app:indicatorColor="@color/accent"
|
||||
android:visibility="invisible"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Diario di bordo"
|
||||
android:layout_marginTop="30dp"/>
|
||||
android:textColor="@color/accent"
|
||||
android:textStyle="bold"/>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/list_events"
|
||||
|
@ -10,6 +10,7 @@ activityCompose = "1.8.0"
|
||||
composeBom = "2024.04.01"
|
||||
appcompat = "1.7.0"
|
||||
recyclerview = "1.3.2"
|
||||
material = "1.12.0"
|
||||
|
||||
[libraries]
|
||||
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||
@ -28,6 +29,7 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
|
||||
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
|
||||
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
|
||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||
|
||||
[plugins]
|
||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||
|
@ -16,6 +16,7 @@ dependencyResolutionManagement {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven(url = "https://jitpack.io")
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user