summaryrefslogtreecommitdiff
path: root/android-app
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-27 16:45:10 +0000
committerClaude <noreply@anthropic.com>2026-05-27 16:45:10 +0000
commit91f5816120a6260fb8f057f6b7c0d6f0f2337c0e (patch)
tree1333a9d46b565ecd45df0cb59d6b336fb6876897 /android-app
parentd7bc194813d98e00190d1c5627965f2fe94f57d9 (diff)
Sort track history chronologically; add Report button to track list
- 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
Diffstat (limited to 'android-app')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt35
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt1
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportFragment.kt27
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/tripreport/TripReportViewModel.kt16
-rw-r--r--android-app/app/src/main/res/layout/item_saved_track.xml15
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<SavedTrackAdapter.VH>() {
private var items: List<SavedTrack> = emptyList()
@@ -106,9 +117,10 @@ internal class SavedTrackAdapter(
}
inner class VH(view: View) : RecyclerView.ViewHolder(view) {
- val tvDate = view.findViewById<TextView>(R.id.tv_track_date)
- val tvStats = view.findViewById<TextView>(R.id.tv_track_stats)
- val tvTacks = view.findViewById<TextView>(R.id.tv_track_tacks)
+ val tvDate = view.findViewById<TextView>(R.id.tv_track_date)
+ val tvStats = view.findViewById<TextView>(R.id.tv_track_stats)
+ val tvTacks = view.findViewById<TextView>(R.id.tv_track_tacks)
+ val btnReport = view.findViewById<MaterialButton>(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" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_track_report"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="REPORT"
+ android:textSize="11sp"
+ android:insetTop="0dp"
+ android:insetBottom="0dp"
+ android:paddingStart="0dp"
+ app:layout_constraintTop_toBottomOf="@id/tv_track_stats"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>