summaryrefslogtreecommitdiff
path: root/android-app/app/src/main/res
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-05-12 13:13:55 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-05-12 13:13:55 -1000
commitf881a1f91f4e9f832ab987fce2b5220e100cee68 (patch)
treecfa3d5e0c0f5e0d1cd2f74a49c20ce8c96b89ae2 /android-app/app/src/main/res
parent1fe6fc2b0c5cabf21c241f9a033e4d53dd475316 (diff)
Add saved tracks browser, tack detection, and map zoom-to-bounds
- TackDetector: sliding-window COG algorithm (60-140deg delta = tack/jibe) with stability check to prevent mixed-window false positives; 13 tests green - TackEvent, SavedTrack: new models wrapping points + summary + detected tacks - TrackRepository.getSavedTracks(): returns List<SavedTrack> with lazy load - MapHandler: zoomToTrackBounds (LatLngBounds fit with padding), panToPoint (log step-through), updateTackLayer (yellow circle markers) - MainViewModel: savedTracks + selectedTrack StateFlows, panToPosition SharedFlow - SavedTracksFragment: RecyclerView list with date/distance/duration/tack count - TrackDetailSheet: bottom sheet with stats + event log (tacks, speed changes, start/end); tapping an entry pans the map to that position - MainActivity: zoom map on track complete, wire Saved Tracks button in instrument sheet, observe panToPosition to drive map, show tack markers when a saved track is selected Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'android-app/app/src/main/res')
-rw-r--r--android-app/app/src/main/res/drawable/ic_back.xml9
-rw-r--r--android-app/app/src/main/res/layout/fragment_saved_tracks.xml54
-rw-r--r--android-app/app/src/main/res/layout/item_log_entry.xml55
-rw-r--r--android-app/app/src/main/res/layout/item_saved_track.xml43
-rw-r--r--android-app/app/src/main/res/layout/layout_instruments_sheet.xml15
-rw-r--r--android-app/app/src/main/res/layout/layout_track_detail_sheet.xml91
6 files changed, 267 insertions, 0 deletions
diff --git a/android-app/app/src/main/res/drawable/ic_back.xml b/android-app/app/src/main/res/drawable/ic_back.xml
new file mode 100644
index 0000000..3b56c6e
--- /dev/null
+++ b/android-app/app/src/main/res/drawable/ic_back.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:fillColor="?attr/colorOnSurface"
+ android:pathData="M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z"/>
+</vector>
diff --git a/android-app/app/src/main/res/layout/fragment_saved_tracks.xml b/android-app/app/src/main/res/layout/fragment_saved_tracks.xml
new file mode 100644
index 0000000..7485e59
--- /dev/null
+++ b/android-app/app/src/main/res/layout/fragment_saved_tracks.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="56dp"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:paddingStart="8dp"
+ android:paddingEnd="16dp"
+ android:elevation="4dp"
+ android:background="?attr/colorSurface">
+
+ <ImageButton
+ android:id="@+id/btn_back"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:src="@drawable/ic_back"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:contentDescription="Back" />
+
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Saved Tracks"
+ android:textSize="18sp"
+ android:textStyle="bold"
+ android:layout_marginStart="4dp" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/tv_empty"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="No saved tracks yet.\nRecord a trip to see it here."
+ android:textAlignment="center"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ android:visibility="gone" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/rv_saved_tracks"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:paddingBottom="80dp" />
+
+</LinearLayout>
diff --git a/android-app/app/src/main/res/layout/item_log_entry.xml b/android-app/app/src/main/res/layout/item_log_entry.xml
new file mode 100644
index 0000000..fc53ef1
--- /dev/null
+++ b/android-app/app/src/main/res/layout/item_log_entry.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:paddingHorizontal="16dp"
+ android:paddingVertical="10dp">
+
+ <TextView
+ android:id="@+id/tv_log_icon"
+ android:layout_width="28dp"
+ android:layout_height="wrap_content"
+ android:textSize="18sp"
+ android:gravity="center"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
+
+ <TextView
+ android:id="@+id/tv_log_time"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ android:layout_marginStart="8dp"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toEndOf="@id/tv_log_icon" />
+
+ <TextView
+ android:id="@+id/tv_log_label"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="14sp"
+ android:textStyle="bold"
+ android:textColor="?attr/colorOnSurface"
+ android:layout_marginStart="8dp"
+ app:layout_constraintTop_toBottomOf="@id/tv_log_time"
+ app:layout_constraintStart_toEndOf="@id/tv_log_icon"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/tv_log_detail"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="12sp"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ android:layout_marginTop="2dp"
+ app:layout_constraintTop_toBottomOf="@id/tv_log_label"
+ app:layout_constraintStart_toEndOf="@id/tv_log_icon"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginStart="8dp" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
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
new file mode 100644
index 0000000..b0a7e24
--- /dev/null
+++ b/android-app/app/src/main/res/layout/item_saved_track.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:padding="16dp">
+
+ <TextView
+ android:id="@+id/tv_track_date"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="15sp"
+ android:textStyle="bold"
+ android:textColor="?attr/colorOnSurface"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toStartOf="@id/tv_track_tacks" />
+
+ <TextView
+ android:id="@+id/tv_track_tacks"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:textColor="#FDD835"
+ android:visibility="gone"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
+ <TextView
+ android:id="@+id/tv_track_stats"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:textSize="13sp"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ android:layout_marginTop="4dp"
+ app:layout_constraintTop_toBottomOf="@id/tv_track_date"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/android-app/app/src/main/res/layout/layout_instruments_sheet.xml b/android-app/app/src/main/res/layout/layout_instruments_sheet.xml
index 59a92c3..f8dc365 100644
--- a/android-app/app/src/main/res/layout/layout_instruments_sheet.xml
+++ b/android-app/app/src/main/res/layout/layout_instruments_sheet.xml
@@ -339,4 +339,19 @@
</LinearLayout>
+ <!-- Saved Tracks button -->
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_saved_tracks"
+ style="@style/Widget.Material3.Button.OutlinedButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginHorizontal="16dp"
+ android:layout_marginTop="12dp"
+ android:layout_marginBottom="8dp"
+ android:text="Saved Tracks"
+ android:textAllCaps="false"
+ app:layout_constraintTop_toBottomOf="@id/forecast_row"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent" />
+
</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/android-app/app/src/main/res/layout/layout_track_detail_sheet.xml b/android-app/app/src/main/res/layout/layout_track_detail_sheet.xml
new file mode 100644
index 0000000..9b2eb30
--- /dev/null
+++ b/android-app/app/src/main/res/layout/layout_track_detail_sheet.xml
@@ -0,0 +1,91 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingBottom="16dp">
+
+ <!-- drag handle -->
+ <View
+ android:layout_width="40dp"
+ android:layout_height="4dp"
+ android:layout_gravity="center_horizontal"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:background="#BDBDBD" />
+
+ <TextView
+ android:id="@+id/detail_title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="20dp"
+ android:paddingBottom="12dp"
+ android:textSize="17sp"
+ android:textStyle="bold" />
+
+ <!-- stats row: distance · duration · max · avg -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingHorizontal="12dp"
+ android:paddingBottom="12dp">
+
+ <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
+ android:layout_weight="1" android:orientation="vertical" android:gravity="center">
+ <TextView android:id="@+id/detail_distance" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:textSize="22sp" android:textStyle="bold" />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="nm" android:textSize="11sp" android:textColor="?attr/colorOnSurfaceVariant" />
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
+ android:layout_weight="1" android:orientation="vertical" android:gravity="center">
+ <TextView android:id="@+id/detail_duration" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:textSize="22sp" android:textStyle="bold" />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="time" android:textSize="11sp" android:textColor="?attr/colorOnSurfaceVariant" />
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
+ android:layout_weight="1" android:orientation="vertical" android:gravity="center">
+ <TextView android:id="@+id/detail_max_sog" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:textSize="22sp" android:textStyle="bold" />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="max kt" android:textSize="11sp" android:textColor="?attr/colorOnSurfaceVariant" />
+ </LinearLayout>
+
+ <LinearLayout android:layout_width="0dp" android:layout_height="wrap_content"
+ android:layout_weight="1" android:orientation="vertical" android:gravity="center">
+ <TextView android:id="@+id/detail_avg_sog" android:layout_width="wrap_content"
+ android:layout_height="wrap_content" android:textSize="22sp" android:textStyle="bold" />
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="avg kt" android:textSize="11sp" android:textColor="?attr/colorOnSurfaceVariant" />
+ </LinearLayout>
+
+ </LinearLayout>
+
+ <View android:layout_width="match_parent" android:layout_height="1dp"
+ android:background="?attr/colorOutlineVariant" android:layout_marginHorizontal="16dp" />
+
+ <!-- event log -->
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:paddingHorizontal="20dp"
+ android:paddingTop="12dp"
+ android:paddingBottom="4dp"
+ android:text="Event Log"
+ android:textSize="13sp"
+ android:textAllCaps="true"
+ android:letterSpacing="0.08"
+ android:textColor="?attr/colorOnSurfaceVariant" />
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/rv_log_entries"
+ android:layout_width="match_parent"
+ android:layout_height="300dp"
+ android:nestedScrollingEnabled="true" />
+
+</LinearLayout>