diff options
Diffstat (limited to 'android-app')
14 files changed, 483 insertions, 18 deletions
diff --git a/android-app/.kotlin/sessions/kotlin-compiler-5367790450239390534.salive b/android-app/.kotlin/sessions/kotlin-compiler-5367790450239390534.salive new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/android-app/.kotlin/sessions/kotlin-compiler-5367790450239390534.salive diff --git a/android-app/.kotlin/sessions/kotlin-compiler-8331847918581383171.salive b/android-app/.kotlin/sessions/kotlin-compiler-8331847918581383171.salive new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/android-app/.kotlin/sessions/kotlin-compiler-8331847918581383171.salive diff --git a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt index 66aa3e0..0f2eb91 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt @@ -30,6 +30,7 @@ import java.util.Locale import org.maplibre.android.MapLibre import org.maplibre.android.maps.MapView import org.maplibre.android.maps.Style +import org.maplibre.android.style.layers.PropertyFactory import org.maplibre.android.style.layers.RasterLayer import org.maplibre.android.style.sources.RasterSource import org.maplibre.android.style.sources.TileSet @@ -335,7 +336,8 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener { lifecycleScope.launch { loadedStyleFlow.filterNotNull() .combine(viewModel.trackPoints) { style, points -> style to points } - .collect { (style, points) -> mapHandler?.updateTrackLayer(style, points) } + .combine(viewModel.pastTracks) { (style, active), past -> Triple(style, active, past) } + .collect { (style, active, past) -> mapHandler?.updateTrackLayer(style, active, past) } } } 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 7953822..85dd2dd 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 @@ -5,22 +5,28 @@ class TrackRepository { var isRecording: Boolean = false private set - private val points = mutableListOf<TrackPoint>() + private val activePoints = mutableListOf<TrackPoint>() + private val pastTracks = mutableListOf<List<TrackPoint>>() fun startTrack() { - points.clear() + activePoints.clear() isRecording = true } fun stopTrack() { + if (isRecording && activePoints.isNotEmpty()) { + pastTracks.add(activePoints.toList()) + } isRecording = false } fun addPoint(point: TrackPoint): Boolean { if (!isRecording) return false - points.add(point) + activePoints.add(point) return true } - fun getPoints(): List<TrackPoint> = points.toList() + fun getPoints(): List<TrackPoint> = activePoints.toList() + + fun getPastTracks(): List<List<TrackPoint>> = pastTracks.toList() } diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripModels.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripModels.kt new file mode 100644 index 0000000..2362079 --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripModels.kt @@ -0,0 +1,43 @@ +package org.terst.nav.tripreport + +enum class BoatType { + MONOHULL, + MULTIHULL +} + +enum class RigType { + SLOOP, + CUTTER, + KETCH +} + +data class BoatProfile( + val name: String, + val lengthFt: Double, + val type: BoatType, + val rig: RigType, + val hasSpinnaker: Boolean = false, + val hasGennaker: Boolean = false +) + +data class PreTripSummary( + val timestampMs: Long, + val lat: Double, + val lon: Double, + val windSpeedKt: Double, + val windDirDeg: Double, + val waveHeightM: Double?, + val weatherDescription: String, + val boatProfile: BoatProfile +) + +data class SailSuggestion( + val sailName: String, + val action: String // e.g., "Full Main", "1 Reef", "Furl" +) + +data class PreTripReport( + val summary: PreTripSummary, + val routingSuggestion: String, + val sailPlan: List<SailSuggestion> +) diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportFragment.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportFragment.kt new file mode 100644 index 0000000..819485f --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportFragment.kt @@ -0,0 +1,102 @@ +package org.terst.nav.tripreport + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +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 com.google.android.material.card.MaterialCardView +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch +import org.terst.nav.R +import org.terst.nav.ui.MainViewModel +import java.util.Locale + +class PreTripReportFragment : Fragment() { + + private val viewModel: PreTripReportViewModel by activityViewModels() + private val mainViewModel: MainViewModel by activityViewModels() + + private lateinit var tvWeatherSummary: TextView + private lateinit var tvRoutingContent: TextView + private lateinit var tvSailPlanContent: TextView + private lateinit var cardReport: MaterialCardView + private lateinit var btnGenerate: MaterialButton + private lateinit var progress: ProgressBar + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? = inflater.inflate(R.layout.fragment_pretrip_report, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + tvWeatherSummary = view.findViewById(R.id.tv_weather_summary) + tvRoutingContent = view.findViewById(R.id.tv_routing_content) + tvSailPlanContent = view.findViewById(R.id.tv_sail_plan_content) + cardReport = view.findViewById(R.id.card_report) + btnGenerate = view.findViewById(R.id.btn_generate_pretrip) + progress = view.findViewById(R.id.progress_pretrip) + + btnGenerate.setOnClickListener { + generateReport() + } + + viewLifecycleOwner.lifecycleScope.launch { + viewModel.state.collect { renderState(it) } + } + } + + private fun generateReport() { + viewLifecycleOwner.lifecycleScope.launch { + val forecast = mainViewModel.forecast.value.firstOrNull() + val conditions = mainViewModel.marineConditions.value + // For now, use 0,0 if no location, but ideally we'd have last known + // In a real app, we'd get this from a LocationProvider + viewModel.generate(0.0, 0.0, forecast, conditions) + } + } + + private fun renderState(state: PreTripState) { + when (state) { + is PreTripState.Loading -> { + progress.visibility = View.VISIBLE + btnGenerate.isEnabled = false + } + is PreTripState.Success -> { + progress.visibility = View.GONE + btnGenerate.isEnabled = true + cardReport.visibility = View.VISIBLE + + val r = state.report + tvWeatherSummary.text = "Wind: %.1f kts from %.0f°\nWaves: %s\nSky: %s".format( + Locale.getDefault(), + r.summary.windSpeedKt, + r.summary.windDirDeg, + r.summary.waveHeightM?.let { "%.1fm".format(it) } ?: "N/A", + r.summary.weatherDescription + ) + + tvRoutingContent.text = r.routingSuggestion + + val sailPlanText = r.sailPlan.joinToString("\n") { + "• ${it.sailName}: ${it.action}" + } + tvSailPlanContent.text = sailPlanText + } + is PreTripState.Error -> { + progress.visibility = View.GONE + btnGenerate.isEnabled = true + // Show toast or error message + } + else -> {} + } + } +} diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportGenerator.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportGenerator.kt new file mode 100644 index 0000000..2ccabfb --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportGenerator.kt @@ -0,0 +1,66 @@ +package org.terst.nav.tripreport + +import org.terst.nav.data.model.ForecastItem +import org.terst.nav.data.model.MarineConditions + +class PreTripReportGenerator { + + fun generateReport( + lat: Double, + lon: Double, + forecast: ForecastItem?, + conditions: MarineConditions?, + boatProfile: BoatProfile + ): PreTripReport { + val summary = PreTripSummary( + timestampMs = System.currentTimeMillis(), + lat = lat, + lon = lon, + windSpeedKt = forecast?.windKt ?: 0.0, + windDirDeg = forecast?.windDirDeg ?: 0.0, + waveHeightM = conditions?.waveHeightM, + weatherDescription = forecast?.weatherDescription() ?: "Unknown", + boatProfile = boatProfile + ) + + val routing = suggestRouting(summary) + val sailPlan = suggestSailPlan(summary) + + return PreTripReport(summary, routing, sailPlan) + } + + private fun suggestRouting(summary: PreTripSummary): String { + val wind = summary.windSpeedKt + val waves = summary.waveHeightM ?: 0.0 + + return when { + wind > 35.0 -> "STORM WARNING: Winds exceed 35kts. Consider remaining in port or seeking shelter immediately." + wind > 25.0 && waves > 2.5 -> "HEAVY WEATHER: Expect challenging conditions. Coastal routing advised to minimize fetch." + wind < 5.0 -> "LIGHT WINDS: Motor-sailing likely required for efficient passage." + else -> "FAVORABLE CONDITIONS: Standard routing based on destination bearing should be effective." + } + } + + private fun suggestSailPlan(summary: PreTripSummary): List<SailSuggestion> { + val wind = summary.windSpeedKt + val suggestions = mutableListOf<SailSuggestion>() + + // Main sail + suggestions.add(when { + wind > 30.0 -> SailSuggestion("Main", "Deep Reef / Trysail") + wind > 22.0 -> SailSuggestion("Main", "2nd Reef") + wind > 16.0 -> SailSuggestion("Main", "1st Reef") + else -> SailSuggestion("Main", "Full Main") + }) + + // Headsail + suggestions.add(when { + wind > 25.0 -> SailSuggestion("Headsail", "Storm Jib / Furl 50%") + wind > 18.0 -> SailSuggestion("Headsail", "Working Jib / Furl 30%") + wind < 10.0 && summary.boatProfile.hasGennaker -> SailSuggestion("Gennaker", "Deploy for light air reach") + else -> SailSuggestion("Headsail", "Full Genoa") + }) + + return suggestions + } +} diff --git a/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportViewModel.kt new file mode 100644 index 0000000..9fd32c7 --- /dev/null +++ b/android-app/app/src/main/kotlin/org/terst/nav/tripreport/PreTripReportViewModel.kt @@ -0,0 +1,51 @@ +package org.terst.nav.tripreport + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import org.terst.nav.data.model.ForecastItem +import org.terst.nav.data.model.MarineConditions + +sealed class PreTripState { + object Idle : PreTripState() + object Loading : PreTripState() + data class Success(val report: PreTripReport) : PreTripState() + data class Error(val message: String) : PreTripState() +} + +class PreTripReportViewModel( + private val generator: PreTripReportGenerator = PreTripReportGenerator() +) : ViewModel() { + + private val _state = MutableStateFlow<PreTripState>(PreTripState.Idle) + val state: StateFlow<PreTripState> = _state.asStateFlow() + + private val _boatProfile = MutableStateFlow( + BoatProfile("Default Sloop", 35.0, BoatType.MONOHULL, RigType.SLOOP) + ) + val boatProfile: StateFlow<BoatProfile> = _boatProfile.asStateFlow() + + fun updateBoatProfile(profile: BoatProfile) { + _boatProfile.value = profile + } + + fun generate( + lat: Double, + lon: Double, + forecast: ForecastItem?, + conditions: MarineConditions? + ) { + viewModelScope.launch { + _state.value = PreTripState.Loading + try { + val report = generator.generateReport(lat, lon, forecast, conditions, _boatProfile.value) + _state.value = PreTripState.Success(report) + } catch (e: Exception) { + _state.value = PreTripState.Error(e.message ?: "Failed to generate pre-trip report") + } + } + } +} diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt index 7caabe7..2c56b06 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt @@ -57,6 +57,9 @@ class MainViewModel( private val _trackPoints = MutableStateFlow<List<TrackPoint>>(emptyList()) val trackPoints: StateFlow<List<TrackPoint>> = _trackPoints.asStateFlow() + private val _pastTracks = MutableStateFlow<List<List<TrackPoint>>>(emptyList()) + val pastTracks: StateFlow<List<List<TrackPoint>>> = _pastTracks.asStateFlow() + fun startTrack() { trackRepository.startTrack() _trackPoints.value = emptyList() @@ -65,6 +68,8 @@ class MainViewModel( fun stopTrack() { trackRepository.stopTrack() + _pastTracks.value = trackRepository.getPastTracks() + _trackPoints.value = emptyList() _isRecording.value = false } diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt index f1feaed..4f08de7 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapHandler.kt @@ -67,14 +67,17 @@ class MapHandler(private val maplibreMap: MapLibreMap) { private val USER_POS_LAYER_ID = "user-pos-layer" private val USER_ICON_ID = "user-icon" - private val TRACK_SOURCE_ID = "track-source" - private val TRACK_LAYER_ID = "track-line" + private val TRACK_ACTIVE_SOURCE_ID = "track-active-source" + private val TRACK_ACTIVE_LAYER_ID = "track-line-active" + private val TRACK_PAST_SOURCE_ID = "track-past-source" + private val TRACK_PAST_LAYER_ID = "track-line-past" private var anchorPointSource: GeoJsonSource? = null private var anchorCircleSource: GeoJsonSource? = null private var tidalCurrentSource: GeoJsonSource? = null private var userPosSource: GeoJsonSource? = null - private var trackSource: GeoJsonSource? = null + private var trackActiveSource: GeoJsonSource? = null + private var trackPastSource: GeoJsonSource? = null /** * Initializes map layers for anchor watch, tidal currents, and user position. @@ -199,26 +202,52 @@ class MapHandler(private val maplibreMap: MapLibreMap) { } /** - * Updates the GPS track polyline on the map. Lazily initialises the layer on first call. + * Updates the GPS track polyline on the map. Lazily initialises the layers on first call. */ - fun updateTrackLayer(style: Style, points: List<TrackPoint>) { - if (trackSource == null) { - trackSource = GeoJsonSource(TRACK_SOURCE_ID) - style.addSource(trackSource!!) - style.addLayer(LineLayer(TRACK_LAYER_ID, TRACK_SOURCE_ID).apply { + fun updateTrackLayer(style: Style, activePoints: List<TrackPoint>, pastTracks: List<List<TrackPoint>>) { + // Active track layer (Solid) + if (trackActiveSource == null) { + trackActiveSource = GeoJsonSource(TRACK_ACTIVE_SOURCE_ID) + style.addSource(trackActiveSource!!) + style.addLayer(LineLayer(TRACK_ACTIVE_LAYER_ID, TRACK_ACTIVE_SOURCE_ID).apply { setProperties( PropertyFactory.lineColor("#E53935"), PropertyFactory.lineWidth(4f), + PropertyFactory.lineCap("round") + ) + }) + } + + // Past tracks layer (Dotted) + if (trackPastSource == null) { + trackPastSource = GeoJsonSource(TRACK_PAST_SOURCE_ID) + style.addSource(trackPastSource!!) + style.addLayer(LineLayer(TRACK_PAST_LAYER_ID, TRACK_PAST_SOURCE_ID).apply { + setProperties( + PropertyFactory.lineColor("#E53935"), + PropertyFactory.lineWidth(3f), PropertyFactory.lineDasharray(arrayOf(1f, 2f)), PropertyFactory.lineCap("round") ) }) } - if (points.size >= 2) { - val coords = points.map { Point.fromLngLat(it.lon, it.lat) } - trackSource?.setGeoJson(Feature.fromGeometry(LineString.fromLngLats(coords))) + + // Update Active Track + if (activePoints.size >= 2) { + val coords = activePoints.map { Point.fromLngLat(it.lon, it.lat) } + trackActiveSource?.setGeoJson(Feature.fromGeometry(LineString.fromLngLats(coords))) + } else { + trackActiveSource?.setGeoJson(FeatureCollection.fromFeatures(emptyList())) + } + + // Update Past Tracks + if (pastTracks.isNotEmpty()) { + val features = pastTracks.map { track -> + Feature.fromGeometry(LineString.fromLngLats(track.map { Point.fromLngLat(it.lon, it.lat) })) + } + trackPastSource?.setGeoJson(FeatureCollection.fromFeatures(features)) } else { - trackSource?.setGeoJson(FeatureCollection.fromFeatures(emptyList())) + trackPastSource?.setGeoJson(FeatureCollection.fromFeatures(emptyList())) } } diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/safety/SafetyFragment.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/safety/SafetyFragment.kt index e950b5d..4bc0c7a 100644 --- a/android-app/app/src/main/kotlin/org/terst/nav/ui/safety/SafetyFragment.kt +++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/safety/SafetyFragment.kt @@ -48,6 +48,13 @@ class SafetyFragment : Fragment() { view.findViewById<MaterialButton>(R.id.button_anchor_config).setOnClickListener { listener?.onConfigureAnchor() } + + view.findViewById<MaterialButton>(R.id.button_plan_trip).setOnClickListener { + parentFragmentManager.beginTransaction() + .replace(R.id.fragment_container, org.terst.nav.tripreport.PreTripReportFragment()) + .addToBackStack(null) + .commit() + } } fun updateAnchorStatus(statusText: String) { 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 86fd67c..1c797d5 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 @@ -16,6 +16,7 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope +import com.google.android.material.button.MaterialButton import com.google.android.material.floatingactionbutton.FloatingActionButton import kotlinx.coroutines.launch import org.terst.nav.R diff --git a/android-app/app/src/main/res/layout/fragment_pretrip_report.xml b/android-app/app/src/main/res/layout/fragment_pretrip_report.xml new file mode 100644 index 0000000..d7ede49 --- /dev/null +++ b/android-app/app/src/main/res/layout/fragment_pretrip_report.xml @@ -0,0 +1,144 @@ +<?xml version="1.0" encoding="utf-8"?> +<ScrollView 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:background="?attr/colorSurface"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="24dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Pre-Trip Planning" + android:textSize="24sp" + android:textStyle="bold" + android:layout_marginBottom="16dp" /> + + <!-- Boat Config (Simple for now) --> + <com.google.android.material.card.MaterialCardView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginBottom="24dp" + app:cardCornerRadius="12dp" + app:strokeWidth="1dp" + app:strokeColor="?attr/colorOutlineVariant"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Vessel Profile" + android:textStyle="bold" + android:layout_marginBottom="8dp" /> + + <TextView + android:id="@+id/tv_vessel_info" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="35ft Sloop (Monohull)" + android:textSize="14sp" /> + + </LinearLayout> + </com.google.android.material.card.MaterialCardView> + + <!-- Report Content --> + <com.google.android.material.card.MaterialCardView + android:id="@+id/card_report" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + app:cardCornerRadius="16dp" + app:cardElevation="4dp"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="20dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Weather Summary" + android:textStyle="bold" + android:textSize="18sp" + android:layout_marginBottom="8dp" /> + + <TextView + android:id="@+id/tv_weather_summary" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="16sp" + android:layout_marginBottom="16dp" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="?attr/colorOutlineVariant" + android:layout_marginBottom="16dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Routing Suggestion" + android:textStyle="bold" + android:textSize="18sp" + android:layout_marginBottom="8dp" /> + + <TextView + android:id="@+id/tv_routing_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="16sp" + android:layout_marginBottom="16dp" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="?attr/colorOutlineVariant" + android:layout_marginBottom="16dp" /> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Sail Plan" + android:textStyle="bold" + android:textSize="18sp" + android:layout_marginBottom="8dp" /> + + <TextView + android:id="@+id/tv_sail_plan_content" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textSize="16sp" /> + + </LinearLayout> + </com.google.android.material.card.MaterialCardView> + + <com.google.android.material.button.MaterialButton + android:id="@+id/btn_generate_pretrip" + android:layout_width="match_parent" + android:layout_height="60dp" + android:layout_marginTop="24dp" + android:text="GENERATE PRE-TRIP REPORT" /> + + <ProgressBar + android:id="@+id/progress_pretrip" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:layout_marginTop="16dp" + android:visibility="gone" /> + + </LinearLayout> +</ScrollView> diff --git a/android-app/app/src/main/res/layout/fragment_safety.xml b/android-app/app/src/main/res/layout/fragment_safety.xml index 5b2397e..f90420e 100644 --- a/android-app/app/src/main/res/layout/fragment_safety.xml +++ b/android-app/app/src/main/res/layout/fragment_safety.xml @@ -104,4 +104,13 @@ </com.google.android.material.card.MaterialCardView> + <com.google.android.material.button.MaterialButton + android:id="@+id/button_plan_trip" + style="@style/Widget.Material3.Button.TonalButton" + android:layout_width="match_parent" + android:layout_height="60dp" + android:layout_marginTop="24dp" + android:text="PLAN TRIP (PRE-TRIP REPORT)" + app:layout_constraintTop_toBottomOf="@id/card_anchor" /> + </androidx.constraintlayout.widget.ConstraintLayout> |
