From e16a5257f5c02034cd9703f465b1ec006d4bb1b0 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 28 May 2026 07:35:00 +0000 Subject: Tech debt: Room list fast-path, delete AnchorWatchHandler orphan, LogbookRepository thread safety MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Room fast-path (item 3): - TrackEntity.toShallowSavedTrack() reconstructs SavedTrack from cached summary data using two placeholder TrackPoints that preserve correct endMs for logbook filtering without parsing GPX - TrackRepository.getSavedTracksFromRoom() returns shallow tracks sorted by startMs - MainViewModel.init{} now shows Room data immediately (no spinner on warm start), then replaces with full GPX-parsed tracks once background load completes AnchorWatchHandler orphan (item 4): - Deleted ui/anchorwatch/AnchorWatchHandler.kt — the anchor depth/rode form was inlined into SafetyFragment; this Fragment had no navigation entry points left - Deleted layout/fragment_anchor_watch.xml (only referenced by the deleted Fragment) LogbookRepository thread safety (item 6): - Added @Synchronized to save(), getAll(), and reload() to prevent concurrent mutation of the entries list and nextId counter across coroutine contexts https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD --- .../org/terst/nav/logbook/LogbookRepository.kt | 3 + .../main/kotlin/org/terst/nav/track/TrackEntity.kt | 16 +++++ .../kotlin/org/terst/nav/track/TrackRepository.kt | 8 +++ .../main/kotlin/org/terst/nav/ui/MainViewModel.kt | 7 ++ .../terst/nav/ui/anchorwatch/AnchorWatchHandler.kt | 58 ---------------- .../src/main/res/layout/fragment_anchor_watch.xml | 79 ---------------------- 6 files changed, 34 insertions(+), 137 deletions(-) delete mode 100644 android-app/app/src/main/kotlin/org/terst/nav/ui/anchorwatch/AnchorWatchHandler.kt delete mode 100644 android-app/app/src/main/res/layout/fragment_anchor_watch.xml (limited to 'android-app/app/src/main') diff --git a/android-app/app/src/main/kotlin/org/terst/nav/logbook/LogbookRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/logbook/LogbookRepository.kt index fe7b40d..06f95b9 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/logbook/LogbookRepository.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/logbook/LogbookRepository.kt @@ -17,6 +17,7 @@ class LogbookRepository(private val storage: LogbookStorage) { /** Moves a temp camera file into persistent storage, deleting the temp. Returns content:// URI string or file path. */ fun importPhoto(file: File): String? = storage.importFile(file) + @Synchronized fun save(entry: LogEntry): LogEntry { val saved = entry.copy(id = nextId++) entries.add(saved) @@ -24,9 +25,11 @@ class LogbookRepository(private val storage: LogbookStorage) { return saved } + @Synchronized fun getAll(): List = entries.toList() /** Re-reads entries from storage. Call after SAF is (re-)authorised post-reinstall. */ + @Synchronized fun reload() { entries.clear() entries.addAll(storage.loadAll()) 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 index 502bfba..b23e159 100644 --- 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 @@ -19,6 +19,22 @@ data class TrackEntity( val endLon: Double ) +/** + * Reconstructs a [SavedTrack] from cached summary data without parsing the GPX file. + * The two placeholder TrackPoints preserve [SavedTrack.endMs] for logbook filtering. + * [SavedTrack.points] contains only start/end; do not use for map rendering or tack detail. + */ +fun TrackEntity.toShallowSavedTrack(): SavedTrack { + val startPoint = TrackPoint(lat = startLat, lon = startLon, sogKnots = 0.0, cogDeg = 0.0, timestampMs = startMs) + val endPoint = TrackPoint(lat = endLat, lon = endLon, sogKnots = 0.0, cogDeg = 0.0, timestampMs = startMs + durationMs) + return SavedTrack( + points = listOf(startPoint, endPoint), + startMs = startMs, + summary = TrackSummary(distanceNm, durationMs, maxSogKt, avgSogKt, avgWindKt, avgWaveHeightM), + tacks = List(tackCount) { TackEvent(it, startLat, startLon, 0.0, 0.0) } + ) +} + fun SavedTrack.toEntity(): TrackEntity = TrackEntity( startMs = startMs, distanceNm = summary.distanceNm, 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 84832f5..4648981 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 @@ -168,6 +168,14 @@ class TrackRepository(context: Context, private val trackDao: TrackDao) { .sortedBy { it.startMs } } + /** + * Fast list from Room cache — no GPX parsing. Returns shallow [SavedTrack] objects + * with only start/end placeholder points. Use [getSavedTracks] for full point data. + */ + suspend fun getSavedTracksFromRoom(): List = withContext(Dispatchers.IO) { + trackDao.getAll().map { it.toShallowSavedTrack() }.sortedBy { it.startMs } + } + fun safState(): SafState = storage.safState() fun initSafDirectory(treeUri: Uri) { diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt index 33366f9..7d0ab33 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt @@ -106,6 +106,13 @@ class MainViewModel( init { viewModelScope.launch { + // Fast path: serve list from Room immediately (no GPX parse) + val roomTracks = runCatching { trackRepository.getSavedTracksFromRoom() }.getOrElse { emptyList() } + if (roomTracks.isNotEmpty()) { + _savedTracks.value = roomTracks + _isLoadingTracks.value = false + } + // Full GPX load — replaces Room data with authoritative tracks (full points) runCatching { trackRepository.getPastTracks() } .onSuccess { _pastTracks.value = it diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/anchorwatch/AnchorWatchHandler.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/anchorwatch/AnchorWatchHandler.kt deleted file mode 100644 index d435f00..0000000 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/anchorwatch/AnchorWatchHandler.kt +++ /dev/null @@ -1,58 +0,0 @@ -package org.terst.nav.ui.anchorwatch - -import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import org.terst.nav.R -import org.terst.nav.databinding.FragmentAnchorWatchBinding -import org.terst.nav.safety.AnchorWatchState - -class AnchorWatchHandler : Fragment() { - - private var _binding: FragmentAnchorWatchBinding? = null - private val binding get() = _binding!! - - private val anchorWatchState = AnchorWatchState() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View { - _binding = FragmentAnchorWatchBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - val watcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit - override fun afterTextChanged(s: Editable?) = updateSuggestedRadius() - } - binding.etDepth.addTextChangedListener(watcher) - binding.etRodeOut.addTextChangedListener(watcher) - } - - private fun updateSuggestedRadius() { - val depth = binding.etDepth.text.toString().toDoubleOrNull() - val rode = binding.etRodeOut.text.toString().toDoubleOrNull() - - if (depth != null && rode != null && depth >= 0.0 && rode > 0.0) { - val radius = AnchorWatchState.calculateRecommendedWatchCircleRadius(depth, 2.0, rode) - binding.tvSuggestedRadius.text = - getString(R.string.anchor_suggested_radius_fmt, radius) - } else { - binding.tvSuggestedRadius.text = getString(R.string.anchor_suggested_radius_empty) - } - } - - override fun onDestroyView() { - super.onDestroyView() - _binding = null - } -} diff --git a/android-app/app/src/main/res/layout/fragment_anchor_watch.xml b/android-app/app/src/main/res/layout/fragment_anchor_watch.xml deleted file mode 100644 index 96b9969..0000000 --- a/android-app/app/src/main/res/layout/fragment_anchor_watch.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - -- cgit v1.2.3