summaryrefslogtreecommitdiff
path: root/android-app/app/src
diff options
context:
space:
mode:
Diffstat (limited to 'android-app/app/src')
-rw-r--r--android-app/app/src/main/AndroidManifest.xml1
-rw-r--r--android-app/app/src/main/assets/docs/getting_started.md18
-rw-r--r--android-app/app/src/main/assets/docs/instruments.md19
-rw-r--r--android-app/app/src/main/assets/docs/safety.md19
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt25
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt18
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt28
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/LayerPickerSheet.kt17
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MapLayerManager.kt45
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/safety/SafetyFragment.kt19
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/voicelog/VoiceLogFragment.kt7
-rw-r--r--android-app/app/src/main/res/layout/activity_main.xml2
-rw-r--r--android-app/app/src/main/res/layout/fragment_safety.xml6
-rw-r--r--android-app/app/src/main/res/layout/fragment_voice_log.xml9
-rw-r--r--android-app/app/src/main/res/layout/layout_layer_picker_sheet.xml69
15 files changed, 250 insertions, 52 deletions
diff --git a/android-app/app/src/main/AndroidManifest.xml b/android-app/app/src/main/AndroidManifest.xml
index 1701e17..6c792f2 100644
--- a/android-app/app/src/main/AndroidManifest.xml
+++ b/android-app/app/src/main/AndroidManifest.xml
@@ -7,6 +7,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
+ <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Documents/ write access — not needed on API 29+ (MediaStore handles it) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
diff --git a/android-app/app/src/main/assets/docs/getting_started.md b/android-app/app/src/main/assets/docs/getting_started.md
new file mode 100644
index 0000000..3e56138
--- /dev/null
+++ b/android-app/app/src/main/assets/docs/getting_started.md
@@ -0,0 +1,18 @@
+# Getting Started with Nav
+
+Welcome to your sailing companion. Nav is designed to provide you with all the essential information you need while on the water.
+
+## Navigation Bar
+Use the navigation bar at the bottom to switch between:
+- **Map:** View your current position, AIS targets, and tidal currents.
+- **Instruments:** View detailed performance data like wind, speed, and depth.
+- **Log:** Access your trip log and record voice entries.
+- **Safety:** Quick access to MOB and Anchor Watch.
+
+## Instrument Panel
+The instrument panel can be expanded from the bottom of the map.
+- **Swipe up** to see more details.
+- **Swipe down** to minimize it and see more of the map.
+
+## Help & Documentation
+You can find more detailed information by tapping the "Help" icon in any of the main screens.
diff --git a/android-app/app/src/main/assets/docs/instruments.md b/android-app/app/src/main/assets/docs/instruments.md
new file mode 100644
index 0000000..bf01ea8
--- /dev/null
+++ b/android-app/app/src/main/assets/docs/instruments.md
@@ -0,0 +1,19 @@
+# Sailing Instruments
+
+The instrument panel provides real-time data from your sensors and boat polars.
+
+## Wind Data
+- **AWS (Apparent Wind Speed):** The wind speed felt by the boat.
+- **TWS (True Wind Speed):** The actual wind speed across the water.
+- **TWD (True Wind Direction):** The compass direction the wind is blowing from.
+
+## Boat Performance
+- **BSP (Boat Speed):** Speed through the water (from your speedo).
+- **SOG (Speed Over Ground):** Actual speed relative to the earth (from GPS).
+- **VMG (Velocity Made Good):** Speed directly upwind or downwind.
+- **Polar %:** Your current speed relative to the theoretical maximum for your boat.
+
+## Navigation
+- **HDG (Heading):** The direction the bow is pointing.
+- **COG (Course Over Ground):** The actual path the boat is tracking (includes leeway and current).
+- **Depth:** Current depth below the keel.
diff --git a/android-app/app/src/main/assets/docs/safety.md b/android-app/app/src/main/assets/docs/safety.md
new file mode 100644
index 0000000..f8bf85d
--- /dev/null
+++ b/android-app/app/src/main/assets/docs/safety.md
@@ -0,0 +1,19 @@
+# Safety First
+
+Nav includes critical safety features that work offline.
+
+## MOB (Man Overboard)
+The **MOB button** is always accessible in the Safety tab.
+1. Tap the large **MOB** button immediately if someone falls overboard.
+2. The app will record the current position and start a timer.
+3. A large arrow will show the bearing and distance back to the MOB point.
+4. An audible alarm will sound continuously.
+5. Tap **RECOVERED** only once the person is safely back on board.
+
+## Anchor Watch
+Monitor your boat's movement while at anchor.
+1. Tap the **Anchor** icon in the Safety tab.
+2. Configure your watch circle radius based on your scope.
+3. Tap **SET ANCHOR** to start monitoring.
+4. If your boat moves outside the circle, a high-priority alarm will sound.
+5. The alarm works in the background even if the screen is off.
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 ead98ae..6c091ff 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
@@ -484,6 +484,10 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener {
fabFishing.visibility = View.GONE
fabLayers.visibility = View.GONE
fabRecenter.visibility = View.GONE
+ if (::bottomSheetBehavior.isInitialized) {
+ bottomSheetBehavior.isHideable = true
+ bottomSheetBehavior.state = BottomSheetBehavior.STATE_HIDDEN
+ }
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
@@ -600,14 +604,25 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener {
private fun checkForegroundPermissions() {
val fineLocation = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
val coarseLocation = ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION)
+ val locationGranted = fineLocation == PackageManager.PERMISSION_GRANTED ||
+ coarseLocation == PackageManager.PERMISSION_GRANTED
+
+ val toRequest = mutableListOf<String>()
+ if (!locationGranted) {
+ toRequest += Manifest.permission.ACCESS_FINE_LOCATION
+ toRequest += Manifest.permission.ACCESS_COARSE_LOCATION
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
+ ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ toRequest += Manifest.permission.POST_NOTIFICATIONS
+ }
- if (fineLocation == PackageManager.PERMISSION_GRANTED || coarseLocation == PackageManager.PERMISSION_GRANTED) {
+ if (locationGranted) {
startServices()
+ if (toRequest.isNotEmpty()) requestPermissionLauncher.launch(toRequest.toTypedArray())
} else {
- requestPermissionLauncher.launch(arrayOf(
- Manifest.permission.ACCESS_FINE_LOCATION,
- Manifest.permission.ACCESS_COARSE_LOCATION
- ))
+ requestPermissionLauncher.launch(toRequest.toTypedArray())
}
}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt b/android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt
index c676390..20de3e4 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt
@@ -7,6 +7,7 @@ enum class TempUnit { CELSIUS, FAHRENHEIT }
enum class DepthUnit { FEET, METERS }
enum class PressureUnit { HPA, INHG }
enum class SpeedUnit { KNOTS, MPH, KPH }
+enum class DistanceUnit { NM, KM, MI }
/**
* Persists and applies the user's preferred display units.
@@ -32,6 +33,10 @@ class UnitPrefs(context: Context) {
get() = runCatching { SpeedUnit.valueOf(prefs.getString(KEY_SPEED, null) ?: "") }.getOrDefault(SpeedUnit.KNOTS)
set(v) { prefs.edit().putString(KEY_SPEED, v.name).apply() }
+ var distanceUnit: DistanceUnit
+ get() = runCatching { DistanceUnit.valueOf(prefs.getString(KEY_DISTANCE, null) ?: "") }.getOrDefault(DistanceUnit.NM)
+ set(v) { prefs.edit().putString(KEY_DISTANCE, v.name).apply() }
+
fun formatTemp(tempC: Double): String = when (tempUnit) {
TempUnit.CELSIUS -> "%.0f".format(Locale.getDefault(), tempC)
TempUnit.FAHRENHEIT -> "%.0f".format(Locale.getDefault(), tempC * 9.0 / 5.0 + 32.0)
@@ -74,10 +79,23 @@ class UnitPrefs(context: Context) {
SpeedUnit.KPH -> "kph"
}
+ fun formatDistance(nm: Double): String = when (distanceUnit) {
+ DistanceUnit.NM -> "%.1f".format(Locale.getDefault(), nm)
+ DistanceUnit.KM -> "%.1f".format(Locale.getDefault(), nm * 1.852)
+ DistanceUnit.MI -> "%.1f".format(Locale.getDefault(), nm * 1.15078)
+ }
+
+ fun distanceUnitLabel(): String = when (distanceUnit) {
+ DistanceUnit.NM -> "nm"
+ DistanceUnit.KM -> "km"
+ DistanceUnit.MI -> "mi"
+ }
+
companion object {
private const val KEY_TEMP = "temp_unit"
private const val KEY_DEPTH = "depth_unit"
private const val KEY_PRESSURE = "pressure_unit"
private const val KEY_SPEED = "speed_unit"
+ private const val KEY_DISTANCE = "distance_unit"
}
}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt
index 7dd0e07..e194efc 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/track/TrackDetailSheet.kt
@@ -352,9 +352,11 @@ class TrackDetailSheet : Fragment() {
"%.0f° → %.0f° (Δ%.0f°)".format(t.cogBefore, t.cogAfter, abs(delta)))
}
- // Log entries saved during or associated with this track's time window
+ // Log entries saved during or associated with this track's time window.
+ // Photo-only entries (no real text) are excluded — photos belong to log entries, not as standalone events.
val logEntries = NavApplication.logbookRepository.getAll()
.filter { it.timestampMs in track.startMs..track.endMs }
+ .filter { it.text.isNotBlank() && it.text != "(photo only)" }
for (entry in logEntries) {
val lat = entry.lat ?: nearestTrackPoint(points, entry.timestampMs)?.lat ?: continue
val lon = entry.lon ?: nearestTrackPoint(points, entry.timestampMs)?.lon ?: continue
@@ -447,17 +449,41 @@ private class LogEventAdapter(
if (bm != null) {
holder.ivThumb.setImageBitmap(bm)
holder.ivThumb.visibility = View.VISIBLE
+ holder.ivThumb.setOnClickListener { showFullscreenPhoto(holder.itemView.context, path) }
} else {
holder.ivThumb.setImageDrawable(null)
holder.ivThumb.visibility = View.GONE
+ holder.ivThumb.setOnClickListener(null)
}
} else {
holder.ivThumb.setImageDrawable(null)
holder.ivThumb.visibility = View.GONE
+ holder.ivThumb.setOnClickListener(null)
}
}
}
+private fun showFullscreenPhoto(context: android.content.Context, pathOrUri: String) {
+ val uri = if (pathOrUri.startsWith("content://")) Uri.parse(pathOrUri) else Uri.fromFile(File(pathOrUri))
+ val imageView = android.widget.ImageView(context).apply {
+ setImageURI(uri)
+ scaleType = android.widget.ImageView.ScaleType.FIT_CENTER
+ setBackgroundColor(android.graphics.Color.BLACK)
+ layoutParams = android.view.ViewGroup.LayoutParams(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT
+ )
+ }
+ android.app.AlertDialog.Builder(context)
+ .setView(imageView)
+ .setPositiveButton("Close", null)
+ .show()
+ .window?.setLayout(
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT,
+ android.view.ViewGroup.LayoutParams.MATCH_PARENT
+ )
+}
+
private fun decodeThumbnail(context: android.content.Context, pathOrUri: String, targetW: Int, targetH: Int): android.graphics.Bitmap? {
if (pathOrUri.startsWith("content://")) {
return runCatching {
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/LayerPickerSheet.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/LayerPickerSheet.kt
index b60af50..47d29c4 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/LayerPickerSheet.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/LayerPickerSheet.kt
@@ -13,6 +13,7 @@ import com.google.android.material.card.MaterialCardView
import com.google.android.material.chip.ChipGroup
import com.google.android.material.switchmaterial.SwitchMaterial
import org.terst.nav.DepthUnit
+import org.terst.nav.DistanceUnit
import org.terst.nav.PressureUnit
import org.terst.nav.R
import org.terst.nav.SpeedUnit
@@ -178,6 +179,22 @@ class LayerPickerSheet(
}
onUnitsChanged()
}
+
+ val chipDistance = view.findViewById<ChipGroup>(R.id.chip_group_distance)
+ chipDistance.check(when (unitPrefs.distanceUnit) {
+ DistanceUnit.NM -> R.id.chip_nm
+ DistanceUnit.KM -> R.id.chip_km
+ DistanceUnit.MI -> R.id.chip_mi
+ })
+ chipDistance.setOnCheckedStateChangeListener { _, ids ->
+ unitPrefs.distanceUnit = when (ids.firstOrNull()) {
+ R.id.chip_nm -> DistanceUnit.NM
+ R.id.chip_km -> DistanceUnit.KM
+ R.id.chip_mi -> DistanceUnit.MI
+ else -> return@setOnCheckedStateChangeListener
+ }
+ onUnitsChanged()
+ }
}
private fun dpToPx(dp: Int): Int =
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapLayerManager.kt b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapLayerManager.kt
index f6ee4fb..8cee462 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/ui/MapLayerManager.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/ui/MapLayerManager.kt
@@ -2,6 +2,8 @@ package org.terst.nav.ui
import android.content.Context
import org.maplibre.android.maps.Style
+import org.maplibre.android.style.layers.BackgroundLayer
+import org.maplibre.android.style.layers.FillLayer
import org.maplibre.android.style.layers.PropertyFactory
import org.maplibre.android.style.layers.RasterLayer
import org.maplibre.android.style.sources.RasterSource
@@ -129,25 +131,38 @@ class MapLayerManager(context: Context) {
}
/**
- * Inserts base-map rasters at indices 0/1/2 so OFM vector layers sit on top.
- * Call this immediately after the style finishes loading.
+ * Inserts base-map rasters above the last OFM fill/background layer so they appear
+ * above OFM water/land fills but below OFM road lines and label symbols.
+ * addLayerAt(0) would put rasters below everything, where OFM fills cover them.
+ * addLayer() (appending) would put rasters on top of labels, hiding them.
+ * addLayerAbove(lastFill) is the sweet spot: imagery shows through, labels overlay.
*/
fun addBaseLayersToStyle(style: Style) {
- android.util.Log.d("MapLayers", "addBaseLayersToStyle preset=$basePreset ofmLayers=${style.layers.size}")
- // Insert in ascending index order so final order from bottom is: ocean(0) → satellite(1) → charts(2) → OFM…
- style.addLayerAt(RasterLayer(LAYER_OCEAN, SOURCE_OCEAN).apply {
- setProperties(PropertyFactory.rasterOpacity(1f))
- setProperties(PropertyFactory.visibility(visibilityFor(basePreset, LAYER_OCEAN)))
- }, 0)
- style.addLayerAt(RasterLayer(LAYER_SATELLITE, SOURCE_SATELLITE).apply {
- setProperties(PropertyFactory.rasterOpacity(1f))
- setProperties(PropertyFactory.visibility(visibilityFor(basePreset, LAYER_SATELLITE)))
- }, 1)
- style.addLayerAt(RasterLayer(LAYER_CHARTS, SOURCE_CHARTS).apply {
+ val lastFillId = style.layers
+ .lastOrNull { it is BackgroundLayer || it is FillLayer }?.id
+ android.util.Log.d("MapLayers", "addBaseLayersToStyle preset=$basePreset ofmLayers=${style.layers.size} insertAbove=$lastFillId")
+
+ fun addBase(layer: RasterLayer) {
+ if (lastFillId != null) style.addLayerAbove(layer, lastFillId)
+ else style.addLayerAt(layer, 0)
+ }
+
+ // Add in reverse visual order (charts first = lowest in our stack)
+ // because each addLayerAbove(x, lastFillId) inserts x just above lastFillId,
+ // pushing previous insertions higher. Final order: ocean → satellite → charts → OFM lines/labels.
+ addBase(RasterLayer(LAYER_CHARTS, SOURCE_CHARTS).apply {
setProperties(PropertyFactory.rasterOpacity(opacityFor(basePreset)))
setProperties(PropertyFactory.visibility(visibilityFor(basePreset, LAYER_CHARTS)))
- }, 2)
- android.util.Log.d("MapLayers", "base layers inserted, ocean=${visibilityFor(basePreset, LAYER_OCEAN)} satellite=${visibilityFor(basePreset, LAYER_SATELLITE)}")
+ })
+ addBase(RasterLayer(LAYER_SATELLITE, SOURCE_SATELLITE).apply {
+ setProperties(PropertyFactory.rasterOpacity(1f))
+ setProperties(PropertyFactory.visibility(visibilityFor(basePreset, LAYER_SATELLITE)))
+ })
+ addBase(RasterLayer(LAYER_OCEAN, SOURCE_OCEAN).apply {
+ setProperties(PropertyFactory.rasterOpacity(1f))
+ setProperties(PropertyFactory.visibility(visibilityFor(basePreset, LAYER_OCEAN)))
+ })
+ android.util.Log.d("MapLayers", "base layers inserted above $lastFillId, ocean=${visibilityFor(basePreset, LAYER_OCEAN)}")
}
/**
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 c3bef8f..91c3489 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
@@ -13,8 +13,10 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.materialswitch.MaterialSwitch
+import org.terst.nav.DepthUnit
import org.terst.nav.NavApplication
import org.terst.nav.R
+import org.terst.nav.UnitPrefs
import org.terst.nav.ais.CpaAlert
import org.terst.nav.ais.CpaSeverity
import org.terst.nav.ais.label
@@ -63,6 +65,11 @@ class SafetyFragment : Fragment() {
chevronAnchor.text = if (expanding) "▼" else "▶"
}
+ val unitPrefs = UnitPrefs(requireContext())
+ val unitLabel = unitPrefs.depthUnitLabel()
+ view.findViewById<TextView>(R.id.tv_anchor_depth_label).text = "Depth ($unitLabel)"
+ view.findViewById<TextView>(R.id.tv_anchor_rode_label).text = "Rode Out ($unitLabel)"
+
val etDepth = view.findViewById<EditText>(R.id.et_anchor_depth)
val etRode = view.findViewById<EditText>(R.id.et_anchor_rode)
val tvRadius = view.findViewById<TextView>(R.id.tv_anchor_suggested_radius)
@@ -70,11 +77,13 @@ class SafetyFragment : Fragment() {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) = Unit
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) = Unit
override fun afterTextChanged(s: Editable?) {
- val depth = etDepth.text.toString().toDoubleOrNull()
- val rode = etRode.text.toString().toDoubleOrNull()
- tvRadius.text = if (depth != null && rode != null && depth >= 0.0 && rode > 0.0) {
- val r = AnchorWatchState.calculateRecommendedWatchCircleRadius(depth, 2.0, rode)
- getString(R.string.anchor_suggested_radius_fmt, r)
+ val inputDepth = etDepth.text.toString().toDoubleOrNull()
+ val inputRode = etRode.text.toString().toDoubleOrNull()
+ tvRadius.text = if (inputDepth != null && inputRode != null && inputDepth >= 0.0 && inputRode > 0.0) {
+ val depthM = if (unitPrefs.depthUnit == DepthUnit.FEET) inputDepth * 0.3048 else inputDepth
+ val rodeM = if (unitPrefs.depthUnit == DepthUnit.FEET) inputRode * 0.3048 else inputRode
+ val r = AnchorWatchState.calculateRecommendedWatchCircleRadius(depthM, 2.0, rodeM)
+ "${unitPrefs.formatDepth(r)} $unitLabel"
} else {
getString(R.string.anchor_suggested_radius_empty)
}
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 51a0685..83e8c07 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
@@ -164,12 +164,7 @@ class VoiceLogFragment : Fragment() {
}
btnClear.setOnClickListener { clearEntry() }
- // Show log-entry section only while recording
- viewLifecycleOwner.lifecycleScope.launch {
- mainViewModel.isRecording.collect { recording ->
- layoutLogEntry.visibility = if (recording) View.VISIBLE else View.GONE
- }
- }
+ layoutLogEntry.visibility = View.VISIBLE
// Log entries list
val tvNoEntries = view.findViewById<TextView>(R.id.tv_no_log_entries)
diff --git a/android-app/app/src/main/res/layout/activity_main.xml b/android-app/app/src/main/res/layout/activity_main.xml
index ed1b7ce..99aa192 100644
--- a/android-app/app/src/main/res/layout/activity_main.xml
+++ b/android-app/app/src/main/res/layout/activity_main.xml
@@ -126,7 +126,7 @@
android:layout_height="wrap_content"
app:behavior_hideable="false"
app:behavior_peekHeight="120dp"
- app:cardElevation="16dp"
+ app:cardElevation="4dp"
app:strokeWidth="0dp"
app:shapeAppearance="@style/ShapeAppearance.Nav.BottomSheet"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
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 78f8754..4a8efa9 100644
--- a/android-app/app/src/main/res/layout/fragment_safety.xml
+++ b/android-app/app/src/main/res/layout/fragment_safety.xml
@@ -4,7 +4,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:background="?attr/colorSurface">
+ android:background="?attr/colorSurface"
+ android:clipToPadding="false"
+ android:paddingBottom="80dp">
<LinearLayout
android:layout_width="match_parent"
@@ -179,6 +181,7 @@
android:layout_marginEnd="8dp">
<TextView
+ android:id="@+id/tv_anchor_depth_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/anchor_depth_label"
@@ -203,6 +206,7 @@
android:layout_marginStart="8dp">
<TextView
+ android:id="@+id/tv_anchor_rode_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/anchor_rode_label"
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 b1c440c..cacc6d6 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
@@ -3,7 +3,9 @@
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:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:paddingBottom="80dp">
<LinearLayout
android:layout_width="match_parent"
@@ -11,13 +13,12 @@
android:orientation="vertical"
android:padding="20dp">
- <!-- ── Log entry section — visible only while recording ── -->
+ <!-- ── Log entry section ── -->
<LinearLayout
android:id="@+id/layout_log_entry"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:visibility="gone">
+ android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
diff --git a/android-app/app/src/main/res/layout/layout_layer_picker_sheet.xml b/android-app/app/src/main/res/layout/layout_layer_picker_sheet.xml
index ce52108..b7cf00e 100644
--- a/android-app/app/src/main/res/layout/layout_layer_picker_sheet.xml
+++ b/android-app/app/src/main/res/layout/layout_layer_picker_sheet.xml
@@ -51,7 +51,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -81,7 +81,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -111,7 +111,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -141,7 +141,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -196,7 +196,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -225,7 +225,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -254,7 +254,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -308,7 +308,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -337,7 +337,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -366,7 +366,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -420,7 +420,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -449,7 +449,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -503,7 +503,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="4dp"
- app:layout_columnWeight="1"
+ android:layout_columnWeight="1"
app:cardCornerRadius="12dp"
app:strokeWidth="0dp">
<LinearLayout
@@ -658,7 +658,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
- android:gravity="center_vertical">
+ android:gravity="center_vertical"
+ android:layout_marginBottom="10dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
@@ -693,5 +694,45 @@
</com.google.android.material.chip.ChipGroup>
</LinearLayout>
+ <!-- Distance -->
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <TextView
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="Distance"
+ android:textSize="14sp"
+ android:textColor="@color/instrument_text_normal" />
+ <com.google.android.material.chip.ChipGroup
+ android:id="@+id/chip_group_distance"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ app:singleSelection="true"
+ app:selectionRequired="true">
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_nm"
+ style="@style/Widget.Material3.Chip.Filter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="nm" />
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_km"
+ style="@style/Widget.Material3.Chip.Filter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="km" />
+ <com.google.android.material.chip.Chip
+ android:id="@+id/chip_mi"
+ style="@style/Widget.Material3.Chip.Filter"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="mi" />
+ </com.google.android.material.chip.ChipGroup>
+ </LinearLayout>
+
</LinearLayout>
</androidx.core.widget.NestedScrollView>