summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/kotlin
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-27 15:41:28 +0000
committerClaude <noreply@anthropic.com>2026-05-27 15:41:28 +0000
commite0df3913fe451056e6c5490f02faf3e8b2f2d33d (patch)
tree901fbf62a6a2e01e0106ab97681a9f1201a82447 /android-app/app/src/main/kotlin
parent6261b85ba8ddd7a7d300f92f40fba185d425d2b6 (diff)
Add Room database as track summary cache
Adds TrackEntity/TrackDao/NavDatabase (Room v2.6.1). TrackRepository writes to Room on stopTrack() and importTrack(), and backfills existing tracks on first cold-start when the Room cache is empty. GPX/SAF remains source-of-truth for reinstall; Room is a fast metadata cache for future list-view optimisation. https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD
Diffstat (limited to 'android-app/app/src/main/kotlin')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/NavApplication.kt6
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/db/NavDatabase.kt26
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackDao.kt25
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackEntity.kt35
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt14
5 files changed, 104 insertions, 2 deletions
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/NavApplication.kt b/android-app/app/src/main/kotlin/org/terst/nav/NavApplication.kt
index 0fe5708..3873002 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/NavApplication.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/NavApplication.kt
@@ -2,6 +2,7 @@ package org.terst.nav
import android.app.Application
import com.google.firebase.crashlytics.FirebaseCrashlytics
+import org.terst.nav.db.NavDatabase
import org.terst.nav.settings.FeatureFlags
import java.io.File
import java.io.PrintWriter
@@ -21,6 +22,8 @@ class NavApplication : Application() {
lateinit var trackRepository: org.terst.nav.track.TrackRepository
lateinit var boatProfileRepository: org.terst.nav.tripreport.BoatProfileRepository
lateinit var vesselRepository: org.terst.nav.vessel.VesselRepository
+ lateinit var database: NavDatabase
+ private set
var isTesting: Boolean = false
get() {
if (field) return true
@@ -36,8 +39,9 @@ class NavApplication : Application() {
override fun onCreate() {
super.onCreate()
featureFlags = FeatureFlags(this)
+ database = NavDatabase.get(this)
logbookRepository = org.terst.nav.logbook.LogbookRepository(org.terst.nav.logbook.LogbookStorage(this))
- trackRepository = org.terst.nav.track.TrackRepository(this)
+ trackRepository = org.terst.nav.track.TrackRepository(this, database.trackDao())
boatProfileRepository = org.terst.nav.tripreport.BoatProfileRepository(this)
vesselRepository = org.terst.nav.vessel.VesselRepository(this)
FirebaseCrashlytics.getInstance().sendUnsentReports()
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/db/NavDatabase.kt b/android-app/app/src/main/kotlin/org/terst/nav/db/NavDatabase.kt
new file mode 100644
index 0000000..ccef58b
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/db/NavDatabase.kt
@@ -0,0 +1,26 @@
+package org.terst.nav.db
+
+import android.content.Context
+import androidx.room.Database
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import org.terst.nav.track.TrackDao
+import org.terst.nav.track.TrackEntity
+
+@Database(entities = [TrackEntity::class], version = 1, exportSchema = false)
+abstract class NavDatabase : RoomDatabase() {
+
+ abstract fun trackDao(): TrackDao
+
+ companion object {
+ @Volatile private var INSTANCE: NavDatabase? = null
+
+ fun get(context: Context): NavDatabase = INSTANCE ?: synchronized(this) {
+ INSTANCE ?: Room.databaseBuilder(
+ context.applicationContext,
+ NavDatabase::class.java,
+ "nav.db"
+ ).build().also { INSTANCE = it }
+ }
+ }
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDao.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDao.kt
new file mode 100644
index 0000000..9c628df
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDao.kt
@@ -0,0 +1,25 @@
+package org.terst.nav.track
+
+import androidx.room.Dao
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
+import androidx.room.Query
+
+@Dao
+interface TrackDao {
+
+ @Query("SELECT * FROM tracks ORDER BY startMs DESC")
+ suspend fun getAll(): List<TrackEntity>
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insert(entity: TrackEntity)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun insertAll(entities: List<TrackEntity>)
+
+ @Query("DELETE FROM tracks")
+ suspend fun clear()
+
+ @Query("SELECT COUNT(*) FROM tracks")
+ suspend fun count(): Int
+}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackEntity.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackEntity.kt
new file mode 100644
index 0000000..502bfba
--- /dev/null
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackEntity.kt
@@ -0,0 +1,35 @@
+package org.terst.nav.track
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "tracks")
+data class TrackEntity(
+ @PrimaryKey val startMs: Long,
+ val distanceNm: Double,
+ val durationMs: Long,
+ val maxSogKt: Double,
+ val avgSogKt: Double,
+ val avgWindKt: Double?,
+ val avgWaveHeightM: Double?,
+ val tackCount: Int,
+ val startLat: Double,
+ val startLon: Double,
+ val endLat: Double,
+ val endLon: Double
+)
+
+fun SavedTrack.toEntity(): TrackEntity = TrackEntity(
+ startMs = startMs,
+ distanceNm = summary.distanceNm,
+ durationMs = summary.durationMs,
+ maxSogKt = summary.maxSogKt,
+ avgSogKt = summary.avgSogKt,
+ avgWindKt = summary.avgWindKt,
+ avgWaveHeightM = summary.avgWaveHeightM,
+ tackCount = tacks.size,
+ startLat = points.first().lat,
+ startLon = points.first().lon,
+ endLat = points.last().lat,
+ endLon = points.last().lon
+)
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
index c40c9c2..019f169 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt
@@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.terst.nav.ui.NavLogger
-class TrackRepository(context: Context) {
+class TrackRepository(context: Context, private val trackDao: TrackDao) {
private val storage = TrackStorage(context)
@@ -58,6 +58,8 @@ class TrackRepository(context: Context) {
Log.e("TrackRepository", "GPX save failed — ${points.size} points will be lost on restart")
NavLogger.e("track", "saveTrack returned false — ${points.size} pts lost on restart")
}
+ val savedTrack = SavedTrack(points, trackStartMs, summary, TackDetector.detectTacks(points))
+ runCatching { trackDao.insert(savedTrack.toEntity()) }
summary
}
@@ -72,6 +74,8 @@ class TrackRepository(context: Context) {
if (saved) {
_pastTracks.add(0, points)
NavLogger.i("track", "importTrack: saved ${points.size} pts startMs=$startMs")
+ val savedTrack = points.toSavedTrack()
+ runCatching { trackDao.insert(savedTrack.toEntity()) }
} else {
NavLogger.e("track", "importTrack: save failed for ${points.size} pts")
}
@@ -125,6 +129,14 @@ class TrackRepository(context: Context) {
_pastTracks.addAll(stored)
NavLogger.i("track", "getPastTracks: total in memory=${_pastTracks.size}")
+ if (stored.isNotEmpty() && trackDao.count() == 0) {
+ // One-time backfill of Room cache from existing GPX files
+ val entities = stored.mapNotNull { pts ->
+ runCatching { pts.toSavedTrack().toEntity() }.getOrNull()
+ }
+ if (entities.isNotEmpty()) trackDao.insertAll(entities)
+ NavLogger.i("track", "getPastTracks: backfilled ${entities.size} tracks into Room")
+ }
}
_pastTracks.toList()
}