summaryrefslogtreecommitdiff
path: root/android-app
diff options
context:
space:
mode:
authorClaude <noreply@anthropic.com>2026-05-20 10:06:14 +0000
committerClaude <noreply@anthropic.com>2026-05-20 10:06:14 +0000
commit3793c919f14d8ae8d8814fde37786418a3dfdae4 (patch)
tree2e789c4975df2bc1ac5791c86c53bc6ba9d94e6b /android-app
parentddae982f561e1f812d55b5943f79dc76144d93d3 (diff)
Inline saved tracks on Log tab; collapse log entry when not recording
Removed the 'Saved Tracks' button that opened a separate overlay. Tracks now appear directly on the Log screen in a RecyclerView below the Generate Trip Report button. Tapping a track hides the overlay and opens the existing TrackDetailSheet bottom sheet over the map. The log entry section (text field, mic, camera, save/clear) is hidden with visibility=gone when not recording and shown when a recording is in progress. The storage-setup prompt is now surfaced here too. SavedTrackAdapter and DATE_FMT widened to internal so VoiceLogFragment can reuse them directly. SavedTracksFragment kept for the GPX import flow. https://claude.ai/code/session_01YUbuZNDAoLea4cf9UGQ9qn
Diffstat (limited to 'android-app')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/SavedTracksFragment.kt4
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt125
-rw-r--r--android-app/app/src/main/res/layout/fragment_voice_log.xml350
3 files changed, 290 insertions, 189 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 f992e1e..44055ce 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
@@ -86,11 +86,11 @@ class SavedTracksFragment : Fragment() {
}
}
-private val DATE_FMT: DateTimeFormatter =
+internal val DATE_FMT: DateTimeFormatter =
DateTimeFormatter.ofPattern("EEE dd MMM yyyy · HH:mm", Locale.getDefault())
.withZone(ZoneId.systemDefault())
-class SavedTrackAdapter(
+internal class SavedTrackAdapter(
private val onSelect: (SavedTrack) -> Unit
) : RecyclerView.Adapter<SavedTrackAdapter.VH>() {
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt
index 105057c..cf6805a 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt
@@ -5,6 +5,7 @@ import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
import android.net.Uri
+import android.os.Build
import android.os.Bundle
import android.speech.RecognitionListener
import android.speech.RecognizerIntent
@@ -18,7 +19,11 @@ import android.widget.TextView
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
+import androidx.recyclerview.widget.DividerItemDecoration
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.google.android.material.floatingactionbutton.FloatingActionButton
import com.google.android.material.textfield.TextInputEditText
@@ -27,17 +32,22 @@ import org.terst.nav.MainActivity
import org.terst.nav.R
import org.terst.nav.logbook.VoiceLogState
import org.terst.nav.logbook.VoiceLogViewModel
-import org.terst.nav.track.SavedTracksFragment
+import org.terst.nav.track.SafState
+import org.terst.nav.track.SavedTrackAdapter
+import org.terst.nav.track.TrackDetailSheet
+import org.terst.nav.ui.MainViewModel
import java.io.File
import java.io.FileOutputStream
import java.util.Locale
class VoiceLogFragment : Fragment() {
- private val viewModel by lazy {
+ private val logViewModel by lazy {
VoiceLogViewModel(repository = org.terst.nav.NavApplication.logbookRepository)
}
+ private val mainViewModel: MainViewModel by activityViewModels()
+ private lateinit var layoutLogEntry: View
private lateinit var speechRecognizer: SpeechRecognizer
private lateinit var etNote: TextInputEditText
private lateinit var fabMic: FloatingActionButton
@@ -50,13 +60,9 @@ class VoiceLogFragment : Fragment() {
private lateinit var btnSave: MaterialButton
private lateinit var btnClear: MaterialButton
private lateinit var tvSavedConfirmation: TextView
- private lateinit var btnGenerateReport: MaterialButton
- /** Path or URI string for the currently attached photo, null if none. */
private var pendingPhotoPath: String? = null
- // ── Camera (TakePicturePreview returns a thumbnail bitmap — no FileProvider needed) ──
-
private val cameraLauncher = registerForActivityResult(
ActivityResultContracts.TakePicturePreview()
) { bitmap: Bitmap? ->
@@ -71,18 +77,12 @@ class VoiceLogFragment : Fragment() {
}
}
- // ── Gallery (GetContent — no storage permission needed; URI valid for this session) ──
-
private val galleryLauncher = registerForActivityResult(
ActivityResultContracts.GetContent()
) { uri: Uri? ->
- if (uri != null) {
- setPhoto(uri.toString()) { ivPhoto.setImageURI(uri) }
- }
+ if (uri != null) setPhoto(uri.toString()) { ivPhoto.setImageURI(uri) }
}
- // ── Lifecycle ─────────────────────────────────────────────────────────────
-
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
@@ -92,18 +92,18 @@ class VoiceLogFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- etNote = view.findViewById(R.id.et_note)
- fabMic = view.findViewById(R.id.fab_mic)
- btnCamera = view.findViewById(R.id.btn_camera)
- btnGallery = view.findViewById(R.id.btn_gallery)
- tvStatus = view.findViewById(R.id.tv_status)
- framePhoto = view.findViewById(R.id.frame_photo)
- ivPhoto = view.findViewById(R.id.iv_photo)
- btnRemovePhoto = view.findViewById(R.id.btn_remove_photo)
- btnSave = view.findViewById(R.id.btn_save)
- btnClear = view.findViewById(R.id.btn_clear)
+ layoutLogEntry = view.findViewById(R.id.layout_log_entry)
+ etNote = view.findViewById(R.id.et_note)
+ fabMic = view.findViewById(R.id.fab_mic)
+ btnCamera = view.findViewById(R.id.btn_camera)
+ btnGallery = view.findViewById(R.id.btn_gallery)
+ tvStatus = view.findViewById(R.id.tv_status)
+ framePhoto = view.findViewById(R.id.frame_photo)
+ ivPhoto = view.findViewById(R.id.iv_photo)
+ btnRemovePhoto = view.findViewById(R.id.btn_remove_photo)
+ btnSave = view.findViewById(R.id.btn_save)
+ btnClear = view.findViewById(R.id.btn_clear)
tvSavedConfirmation = view.findViewById(R.id.tv_saved_confirmation)
- btnGenerateReport = view.findViewById(R.id.btn_generate_report)
setupSpeechRecognizer()
@@ -111,27 +111,75 @@ class VoiceLogFragment : Fragment() {
btnCamera.setOnClickListener { cameraLauncher.launch(null) }
btnGallery.setOnClickListener { galleryLauncher.launch("image/*") }
btnRemovePhoto.setOnClickListener { clearPhoto() }
-
btnSave.setOnClickListener {
- val text = etNote.text?.toString()?.trim() ?: ""
- viewModel.save(text, pendingPhotoPath)
+ logViewModel.save(etNote.text?.toString()?.trim() ?: "", pendingPhotoPath)
}
-
btnClear.setOnClickListener { clearEntry() }
- btnGenerateReport.setOnClickListener {
+ view.findViewById<MaterialButton>(R.id.btn_generate_report).setOnClickListener {
parentFragmentManager.beginTransaction()
.replace(R.id.fragment_container, org.terst.nav.tripreport.TripReportFragment())
.addToBackStack(null)
.commit()
}
- view.findViewById<MaterialButton>(R.id.btn_saved_tracks).setOnClickListener {
- (requireActivity() as MainActivity).showSavedTracks()
+ // Show log-entry section only while recording
+ viewLifecycleOwner.lifecycleScope.launch {
+ mainViewModel.isRecording.collect { recording ->
+ layoutLogEntry.visibility = if (recording) View.VISIBLE else View.GONE
+ }
+ }
+
+ // Inline track list
+ val rv = view.findViewById<RecyclerView>(R.id.rv_saved_tracks)
+ val layoutEmpty = view.findViewById<View>(R.id.layout_empty)
+ val tvEmpty = view.findViewById<TextView>(R.id.tv_empty)
+ val btnSetup = view.findViewById<MaterialButton>(R.id.btn_setup_storage)
+
+ rv.layoutManager = LinearLayoutManager(requireContext())
+ rv.isNestedScrollingEnabled = false
+ rv.addItemDecoration(DividerItemDecoration(requireContext(), DividerItemDecoration.VERTICAL))
+
+ val tracksAdapter = SavedTrackAdapter { track ->
+ mainViewModel.selectTrack(track)
+ (requireActivity() as MainActivity).hideOverlays()
+ TrackDetailSheet.newInstance().show(parentFragmentManager, "track_detail")
+ }
+ rv.adapter = tracksAdapter
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ mainViewModel.savedTracks.collect { tracks ->
+ tracksAdapter.submitList(tracks)
+ layoutEmpty.visibility = if (tracks.isEmpty()) View.VISIBLE else View.GONE
+ rv.visibility = if (tracks.isEmpty()) View.GONE else View.VISIBLE
+ }
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ viewLifecycleOwner.lifecycleScope.launch {
+ mainViewModel.safState.collect { state ->
+ when (state) {
+ SafState.NOT_CONFIGURED -> {
+ tvEmpty.text = "No saved tracks yet.\nRecord a trip to see it here."
+ btnSetup.text = "Set up track storage"
+ btnSetup.visibility = View.VISIBLE
+ }
+ SafState.PERMISSION_LOST -> {
+ tvEmpty.text = "Track storage needs re-authorization."
+ btnSetup.text = "Restore track access"
+ btnSetup.visibility = View.VISIBLE
+ }
+ SafState.CONFIGURED -> btnSetup.visibility = View.GONE
+ }
+ }
+ }
+ btnSetup.setOnClickListener {
+ (requireActivity() as MainActivity).launchSafPicker()
+ }
}
viewLifecycleOwner.lifecycleScope.launch {
- viewModel.state.collect { state -> renderState(state) }
+ logViewModel.state.collect { state -> renderState(state) }
}
}
@@ -145,16 +193,14 @@ class VoiceLogFragment : Fragment() {
}
speechRecognizer = SpeechRecognizer.createSpeechRecognizer(requireContext())
speechRecognizer.setRecognitionListener(object : RecognitionListener {
- override fun onReadyForSpeech(params: Bundle?) { viewModel.onListeningStarted() }
+ override fun onReadyForSpeech(params: Bundle?) { logViewModel.onListeningStarted() }
override fun onResults(results: Bundle?) {
val text = results?.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION)
?.firstOrNull() ?: ""
- if (text.isNotBlank()) viewModel.onSpeechRecognized(text)
- else viewModel.onRecognitionError("Could not understand speech")
- }
- override fun onError(error: Int) {
- viewModel.onRecognitionError("Recognition error: $error")
+ if (text.isNotBlank()) logViewModel.onSpeechRecognized(text)
+ else logViewModel.onRecognitionError("Could not understand speech")
}
+ override fun onError(error: Int) { logViewModel.onRecognitionError("Recognition error: $error") }
override fun onBeginningOfSpeech() {}
override fun onBufferReceived(b: ByteArray?) {}
override fun onEndOfSpeech() {}
@@ -197,7 +243,7 @@ class VoiceLogFragment : Fragment() {
private fun clearEntry() {
etNote.setText("")
clearPhoto()
- viewModel.retry()
+ logViewModel.retry()
tvSavedConfirmation.text = ""
tvStatus.text = ""
}
@@ -215,7 +261,6 @@ class VoiceLogFragment : Fragment() {
fabMic.isEnabled = false
}
is VoiceLogState.Result -> {
- // Fill the text field; user can edit before saving
etNote.setText(state.recognized)
etNote.setSelection(state.recognized.length)
tvStatus.text = ""
diff --git a/android-app/app/src/main/res/layout/fragment_voice_log.xml b/android-app/app/src/main/res/layout/fragment_voice_log.xml
index 16c3574..389add1 100644
--- a/android-app/app/src/main/res/layout/fragment_voice_log.xml
+++ b/android-app/app/src/main/res/layout/fragment_voice_log.xml
@@ -1,165 +1,221 @@
<?xml version="1.0" encoding="utf-8"?>
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<androidx.core.widget.NestedScrollView
+ 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="match_parent"
- android:orientation="vertical"
- android:padding="20dp">
+ android:layout_height="match_parent">
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:text="Log Entry"
- android:textAppearance="?attr/textAppearanceHeadlineSmall"
- android:layout_marginBottom="16dp" />
-
- <!-- Note text — voice fills this; user can also type directly -->
- <com.google.android.material.textfield.TextInputLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dp"
- style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
-
- <com.google.android.material.textfield.TextInputEditText
- android:id="@+id/et_note"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:minLines="3"
- android:maxLines="6"
- android:gravity="top"
- android:hint="Tap mic or type a note…"
- android:inputType="textMultiLine|textCapSentences"
- android:scrollbars="vertical" />
-
- </com.google.android.material.textfield.TextInputLayout>
-
- <!-- Action row: mic, camera, gallery, status -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:layout_marginBottom="12dp">
+ android:orientation="vertical"
+ android:padding="20dp">
- <com.google.android.material.floatingactionbutton.FloatingActionButton
- android:id="@+id/fab_mic"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- app:fabSize="mini"
- android:src="@android:drawable/ic_btn_speak_now"
- android:contentDescription="Start voice recognition"
- android:layout_marginEnd="12dp" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/btn_camera"
- style="@style/Widget.Material3.Button.IconButton.Outlined"
- android:layout_width="wrap_content"
+ <!-- ── Log entry section — visible only while recording ── -->
+ <LinearLayout
+ android:id="@+id/layout_log_entry"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:contentDescription="Take photo"
- app:icon="@android:drawable/ic_menu_camera"
- android:layout_marginEnd="8dp" />
-
+ android:orientation="vertical"
+ android:visibility="gone">
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Log Entry"
+ android:textAppearance="?attr/textAppearanceHeadlineSmall"
+ android:layout_marginBottom="16dp" />
+
+ <com.google.android.material.textfield.TextInputLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ style="@style/Widget.Material3.TextInputLayout.OutlinedBox">
+
+ <com.google.android.material.textfield.TextInputEditText
+ android:id="@+id/et_note"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:minLines="3"
+ android:maxLines="6"
+ android:gravity="top"
+ android:hint="Tap mic or type a note…"
+ android:inputType="textMultiLine|textCapSentences"
+ android:scrollbars="vertical" />
+
+ </com.google.android.material.textfield.TextInputLayout>
+
+ <!-- Action row: mic, camera, gallery, status -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginBottom="12dp">
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/fab_mic"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:fabSize="mini"
+ android:src="@android:drawable/ic_btn_speak_now"
+ android:contentDescription="Start voice recognition"
+ android:layout_marginEnd="12dp" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_camera"
+ style="@style/Widget.Material3.Button.IconButton.Outlined"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="Take photo"
+ app:icon="@android:drawable/ic_menu_camera"
+ android:layout_marginEnd="8dp" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_gallery"
+ style="@style/Widget.Material3.Button.IconButton.Outlined"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="Attach photo from gallery"
+ app:icon="@android:drawable/ic_menu_gallery"
+ android:layout_marginEnd="12dp" />
+
+ <TextView
+ android:id="@+id/tv_status"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text=""
+ android:textSize="14sp"
+ android:textColor="?attr/colorOnSurfaceVariant" />
+
+ </LinearLayout>
+
+ <!-- Photo thumbnail — hidden until a photo is attached -->
+ <FrameLayout
+ android:id="@+id/frame_photo"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginBottom="12dp"
+ android:visibility="gone">
+
+ <ImageView
+ android:id="@+id/iv_photo"
+ android:layout_width="120dp"
+ android:layout_height="90dp"
+ android:scaleType="centerCrop"
+ android:contentDescription="Attached photo" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_remove_photo"
+ style="@style/Widget.Material3.Button.IconButton"
+ android:layout_width="32dp"
+ android:layout_height="32dp"
+ android:layout_gravity="top|end"
+ android:contentDescription="Remove photo"
+ app:icon="@drawable/ic_close" />
+
+ </FrameLayout>
+
+ <!-- Save / Clear row -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical"
+ android:layout_marginBottom="12dp">
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_save"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Save"
+ android:layout_marginEnd="8dp" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_clear"
+ style="@style/Widget.Material3.Button.TextButton"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Clear" />
+
+ </LinearLayout>
+
+ <TextView
+ android:id="@+id/tv_saved_confirmation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text=""
+ android:textSize="14sp"
+ android:textColor="?attr/colorPrimary"
+ android:layout_marginBottom="20dp" />
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?attr/colorOutline"
+ android:layout_marginBottom="20dp" />
+
+ </LinearLayout>
+ <!-- end layout_log_entry -->
+
+ <!-- Generate Trip Report — always accessible -->
<com.google.android.material.button.MaterialButton
- android:id="@+id/btn_gallery"
- style="@style/Widget.Material3.Button.IconButton.Outlined"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:contentDescription="Attach photo from gallery"
- app:icon="@android:drawable/ic_menu_gallery"
- android:layout_marginEnd="12dp" />
+ android:id="@+id/btn_generate_report"
+ style="@style/Widget.Material3.Button.TonalButton"
+ android:layout_width="match_parent"
+ android:layout_height="60dp"
+ android:text="GENERATE TRIP REPORT"
+ android:layout_marginBottom="24dp" />
+ <!-- ── Trips section ── -->
<TextView
- android:id="@+id/tv_status"
- android:layout_width="0dp"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text=""
- android:textSize="14sp"
- android:textColor="?attr/colorOnSurfaceVariant" />
-
- </LinearLayout>
-
- <!-- Photo thumbnail — hidden until a photo is attached -->
- <FrameLayout
- android:id="@+id/frame_photo"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="12dp"
- android:visibility="gone">
-
- <ImageView
- android:id="@+id/iv_photo"
- android:layout_width="120dp"
- android:layout_height="90dp"
- android:scaleType="centerCrop"
- android:contentDescription="Attached photo" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/btn_remove_photo"
- style="@style/Widget.Material3.Button.IconButton"
- android:layout_width="32dp"
- android:layout_height="32dp"
- android:layout_gravity="top|end"
- android:contentDescription="Remove photo"
- app:icon="@drawable/ic_close" />
-
- </FrameLayout>
-
- <!-- Save / Clear row -->
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical"
- android:layout_marginBottom="12dp">
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/btn_save"
- android:layout_width="0dp"
+ android:text="Trips"
+ android:textSize="13sp"
+ android:textAllCaps="true"
+ android:letterSpacing="0.08"
+ android:textColor="?attr/colorOnSurfaceVariant"
+ android:layout_marginBottom="8dp" />
+
+ <!-- Empty state -->
+ <LinearLayout
+ android:id="@+id/layout_empty"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="1"
- android:text="Save"
- android:layout_marginEnd="8dp" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/btn_clear"
- style="@style/Widget.Material3.Button.TextButton"
- android:layout_width="wrap_content"
+ android:orientation="vertical"
+ android:gravity="center_horizontal"
+ android:paddingVertical="24dp"
+ android:visibility="gone">
+
+ <TextView
+ android:id="@+id/tv_empty"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="No saved tracks yet.\nRecord a trip to see it here."
+ android:textAlignment="center"
+ android:textColor="?attr/colorOnSurfaceVariant" />
+
+ <com.google.android.material.button.MaterialButton
+ android:id="@+id/btn_setup_storage"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="16dp"
+ android:text="Set up track storage"
+ android:visibility="gone" />
+
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/rv_saved_tracks"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:text="Clear" />
+ android:nestedScrollingEnabled="false"
+ android:clipToPadding="false"
+ android:paddingBottom="80dp" />
</LinearLayout>
- <!-- Saved confirmation -->
- <TextView
- android:id="@+id/tv_saved_confirmation"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text=""
- android:textSize="14sp"
- android:textColor="?attr/colorPrimary"
- android:layout_marginBottom="24dp" />
-
- <View
- android:layout_width="match_parent"
- android:layout_height="1dp"
- android:background="?attr/colorOutline"
- android:layout_marginBottom="24dp" />
-
- <com.google.android.material.button.MaterialButton
- android:id="@+id/btn_generate_report"
- style="@style/Widget.Material3.Button.TonalButton"
- android:layout_width="match_parent"
- android:layout_height="60dp"
- android:text="GENERATE TRIP REPORT" />
-
- <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_marginTop="12dp"
- android:text="Saved Tracks"
- android:textAllCaps="false" />
-
-</LinearLayout>
+</androidx.core.widget.NestedScrollView>