diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-05-12 13:13:55 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-05-12 13:13:55 -1000 |
| commit | f881a1f91f4e9f832ab987fce2b5220e100cee68 (patch) | |
| tree | cfa3d5e0c0f5e0d1cd2f74a49c20ce8c96b89ae2 /android-app/app/src/main/res | |
| parent | 1fe6fc2b0c5cabf21c241f9a033e4d53dd475316 (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')
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> |
