diff options
Diffstat (limited to 'android-app/app')
6 files changed, 109 insertions, 2 deletions
diff --git a/android-app/app/build.gradle b/android-app/app/build.gradle index bf8d5c9..dc06536 100644 --- a/android-app/app/build.gradle +++ b/android-app/app/build.gradle @@ -97,6 +97,11 @@ dependencies { implementation 'com.squareup.moshi:moshi-kotlin:1.15.0' ksp 'com.squareup.moshi:moshi-kotlin-codegen:1.15.0' + // Room + implementation 'androidx.room:room-runtime:2.6.1' + implementation 'androidx.room:room-ktx:2.6.1' + ksp 'androidx.room:room-compiler:2.6.1' + // Location implementation 'com.google.android.gms:play-services-location:21.2.0' 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() } |
