From 91f5816120a6260fb8f057f6b7c0d6f0f2337c0e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 27 May 2026 16:45:10 +0000 Subject: Sort track history chronologically; add Report button to track list MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getSavedTracks() now returns tracks sorted by startMs ascending so the overview list shows oldest→newest - item_saved_track.xml gets a small REPORT text button below the stats row - SavedTrackAdapter wires onReport callback; SavedTracksFragment navigates to TripReportFragment.newInstanceForSavedTrack() after selecting the track - TripReportViewModel.generateReportForSavedTrack(track) generates a post-trip narrative from a completed SavedTrack's points + logbook entries - TripReportFragment detects ARG_FOR_SAVED_TRACK, reads selectedTrack from MainViewModel, and uses the saved-track variant; Refresh button re-runs the same variant https://claude.ai/code/session_01KXYBuAHZkvv63DeUG6XaZD --- .../org/terst/nav/track/SavedTracksFragment.kt | 35 +++++++++++++++------- .../kotlin/org/terst/nav/track/TrackRepository.kt | 1 + .../org/terst/nav/tripreport/TripReportFragment.kt | 27 +++++++++++++++-- .../terst/nav/tripreport/TripReportViewModel.kt | 16 ++++++++++ .../app/src/main/res/layout/item_saved_track.xml | 15 +++++++++- 5 files changed, 79 insertions(+), 15 deletions(-) diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt index fca83cd..76460f9 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.launch import org.terst.nav.MainActivity import org.terst.nav.R +import org.terst.nav.tripreport.TripReportFragment import org.terst.nav.ui.MainViewModel import java.time.Instant import java.time.ZoneId @@ -43,13 +44,22 @@ class SavedTracksFragment : Fragment() { rv.layoutManager = LinearLayoutManager(requireContext()) rv.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL)) - val adapter = SavedTrackAdapter { track -> - viewModel.selectTrack(track) - parentFragmentManager.beginTransaction() - .replace(R.id.fragment_container, TrackDetailSheet.newInstance()) - .addToBackStack("track_detail") - .commit() - } + val adapter = SavedTrackAdapter( + onSelect = { track -> + viewModel.selectTrack(track) + parentFragmentManager.beginTransaction() + .replace(R.id.fragment_container, TrackDetailSheet.newInstance()) + .addToBackStack("track_detail") + .commit() + }, + onReport = { track -> + viewModel.selectTrack(track) + parentFragmentManager.beginTransaction() + .replace(R.id.fragment_container, TripReportFragment.newInstanceForSavedTrack()) + .addToBackStack("trip_report") + .commit() + } + ) rv.adapter = adapter viewLifecycleOwner.lifecycleScope.launch { @@ -95,7 +105,8 @@ internal val DATE_FMT: DateTimeFormatter = .withZone(ZoneId.systemDefault()) internal class SavedTrackAdapter( - private val onSelect: (SavedTrack) -> Unit + private val onSelect: (SavedTrack) -> Unit, + private val onReport: (SavedTrack) -> Unit ) : RecyclerView.Adapter() { private var items: List = emptyList() @@ -106,9 +117,10 @@ internal class SavedTrackAdapter( } inner class VH(view: View) : RecyclerView.ViewHolder(view) { - val tvDate = view.findViewById(R.id.tv_track_date) - val tvStats = view.findViewById(R.id.tv_track_stats) - val tvTacks = view.findViewById(R.id.tv_track_tacks) + val tvDate = view.findViewById(R.id.tv_track_date) + val tvStats = view.findViewById(R.id.tv_track_stats) + val tvTacks = view.findViewById(R.id.tv_track_tacks) + val btnReport = view.findViewById(R.id.btn_track_report) } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH = @@ -134,5 +146,6 @@ internal class SavedTrackAdapter( } holder.itemView.setOnClickListener { onSelect(track) } + holder.btnReport.setOnClickListener { onReport(track) } } } 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 019f169..84832f5 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 @@ -165,6 +165,7 @@ class TrackRepository(context: Context, private val trackDao: TrackDao) { .onFailure { e -> NavLogger.e("track", "toSavedTrack failed: ${e.javaClass.simpleName}: ${e.message}") } .getOrNull() } + .sortedBy { it.startMs } } fun safState(): SafState = storage.safState() diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportFragment.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportFragment.kt index d567ed6..0a112a8 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportFragment.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportFragment.kt @@ -7,11 +7,13 @@ import android.view.ViewGroup import android.widget.ProgressBar import android.widget.TextView import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope import com.google.android.material.button.MaterialButton import kotlinx.coroutines.launch import org.terst.nav.NavApplication import org.terst.nav.R +import org.terst.nav.ui.MainViewModel class TripReportFragment : Fragment() { @@ -22,6 +24,18 @@ class TripReportFragment : Fragment() { ) } + private val mainViewModel: MainViewModel by activityViewModels() + + companion object { + private const val ARG_FOR_SAVED_TRACK = "for_saved_track" + + fun newInstance(): TripReportFragment = TripReportFragment() + + fun newInstanceForSavedTrack(): TripReportFragment = TripReportFragment().apply { + arguments = Bundle().also { it.putBoolean(ARG_FOR_SAVED_TRACK, true) } + } + } + private lateinit var tvReportTitle: TextView private lateinit var tvNarrativeContent: TextView private lateinit var btnRefresh: MaterialButton @@ -44,7 +58,16 @@ class TripReportFragment : Fragment() { btnRefresh = view.findViewById(R.id.btn_refresh_report) progress = view.findViewById(R.id.progress_report) - btnRefresh.setOnClickListener { viewModel.generateReport() } + val forSavedTrack = arguments?.getBoolean(ARG_FOR_SAVED_TRACK, false) == true + val savedTrack = if (forSavedTrack) mainViewModel.selectedTrack.value else null + + if (savedTrack != null) { + viewModel.generateReportForSavedTrack(savedTrack) + btnRefresh.setOnClickListener { viewModel.generateReportForSavedTrack(savedTrack) } + } else { + viewModel.generateReport() + btnRefresh.setOnClickListener { viewModel.generateReport() } + } tvReportTitle.setOnLongClickListener { pirateMode = !pirateMode @@ -59,8 +82,6 @@ class TripReportFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewModel.state.collect { state -> renderState(state) } } - - viewModel.generateReport() } private fun renderState(state: TripReportState) { diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportViewModel.kt index 603d769..fbe1a30 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportViewModel.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportViewModel.kt @@ -7,6 +7,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import org.terst.nav.logbook.LogbookRepository +import org.terst.nav.track.SavedTrack import org.terst.nav.track.TrackRepository sealed class TripReportState { @@ -43,4 +44,19 @@ class TripReportViewModel( } } } + + fun generateReportForSavedTrack(track: SavedTrack) { + viewModelScope.launch { + _state.value = TripReportState.Loading + try { + val logEntries = logbookRepository.getAll() + .filter { it.timestampMs in track.startMs..track.endMs } + val summary = generator.generateSummary(track.points, logEntries) + val narrative = generator.generateNarrative(summary) + _state.value = TripReportState.Success(summary, narrative) + } catch (e: Exception) { + _state.value = TripReportState.Error(e.message ?: "Unknown error generating report") + } + } + } } diff --git a/android-app/app/src/main/res/layout/item_saved_track.xml b/android-app/app/src/main/res/layout/item_saved_track.xml index b0a7e24..681220e 100644 --- a/android-app/app/src/main/res/layout/item_saved_track.xml +++ b/android-app/app/src/main/res/layout/item_saved_track.xml @@ -37,7 +37,20 @@ android:layout_marginTop="4dp" app:layout_constraintTop_toBottomOf="@id/tv_track_date" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintEnd_toEndOf="parent" /> + + -- cgit v1.2.3