summaryrefslogtreecommitdiff
path: root/android-app/app
diff options
context:
space:
mode:
authorPeter Stone <thepeterstone@gmail.com>2026-05-07 08:58:57 -1000
committerPeter Stone <thepeterstone@gmail.com>2026-05-07 08:58:57 -1000
commitd8a53c3c9cba34dec7741395877a8dc87cebd0c5 (patch)
treecb50ab2d4a0a0e443d9b42249420ece53319ec9c /android-app/app
parent29fb98a20923b1b04a126ca00a2c21448ed822d5 (diff)
refactor: extract GeoThrottle, fix fully-qualified refs, harden enum parsing
- Extract GeoThrottle inner class in MainViewModel; eliminates 6 state vars and duplicate throttle logic in loadConditions/loadWindGrid - Fix android.util.Log.e and kotlin.math.* fully-qualified refs now that imports are present - UnitPrefs: wrap valueOf() in runCatching to handle corrupt pref values - VesselRepository.init: forEach { add() } → mapTo() for both lists - TrackRepository: remove noisy cached-log else-branch that fired on every stopTrack() call Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'android-app/app')
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/MainActivity.kt6
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/UnitPrefs.kt8
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/track/TrackRepository.kt2
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/ui/MainViewModel.kt60
-rw-r--r--android-app/app/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt4
5 files changed, 40 insertions, 40 deletions
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 5b219c4..39e0696 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
@@ -27,6 +27,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import java.util.Locale
import org.maplibre.android.MapLibre
@@ -60,7 +62,7 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener {
private lateinit var layerManager: MapLayerManager
private var particleWindView: ParticleWindView? = null
private var weatherLoaded = false
- private var cameraIdleJob: kotlinx.coroutines.Job? = null
+ private var cameraIdleJob: Job? = null
private lateinit var bottomSheetBehavior: BottomSheetBehavior<View>
private lateinit var fragmentContainer: FrameLayout
@@ -497,7 +499,7 @@ class MainActivity : AppCompatActivity(), SafetyFragment.SafetyListener {
maplibreMap.addOnCameraIdleListener {
cameraIdleJob?.cancel()
cameraIdleJob = lifecycleScope.launch {
- kotlinx.coroutines.delay(1_500)
+ delay(1_500)
val corners = listOfNotNull(
maplibreMap.projection.visibleRegion.nearLeft,
maplibreMap.projection.visibleRegion.nearRight,
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 c0745f8..c676390 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
@@ -17,19 +17,19 @@ class UnitPrefs(context: Context) {
private val prefs = context.getSharedPreferences("unit_prefs", Context.MODE_PRIVATE)
var tempUnit: TempUnit
- get() = TempUnit.valueOf(prefs.getString(KEY_TEMP, null) ?: TempUnit.FAHRENHEIT.name)
+ get() = runCatching { TempUnit.valueOf(prefs.getString(KEY_TEMP, null) ?: "") }.getOrDefault(TempUnit.FAHRENHEIT)
set(v) { prefs.edit().putString(KEY_TEMP, v.name).apply() }
var depthUnit: DepthUnit
- get() = DepthUnit.valueOf(prefs.getString(KEY_DEPTH, null) ?: DepthUnit.FEET.name)
+ get() = runCatching { DepthUnit.valueOf(prefs.getString(KEY_DEPTH, null) ?: "") }.getOrDefault(DepthUnit.FEET)
set(v) { prefs.edit().putString(KEY_DEPTH, v.name).apply() }
var pressureUnit: PressureUnit
- get() = PressureUnit.valueOf(prefs.getString(KEY_PRESSURE, null) ?: PressureUnit.HPA.name)
+ get() = runCatching { PressureUnit.valueOf(prefs.getString(KEY_PRESSURE, null) ?: "") }.getOrDefault(PressureUnit.HPA)
set(v) { prefs.edit().putString(KEY_PRESSURE, v.name).apply() }
var speedUnit: SpeedUnit
- get() = SpeedUnit.valueOf(prefs.getString(KEY_SPEED, null) ?: SpeedUnit.KNOTS.name)
+ get() = runCatching { SpeedUnit.valueOf(prefs.getString(KEY_SPEED, null) ?: "") }.getOrDefault(SpeedUnit.KNOTS)
set(v) { prefs.edit().putString(KEY_SPEED, v.name).apply() }
fun formatTemp(tempC: Double): String = when (tempUnit) {
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 bf25dfc..1b4cf34 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
@@ -96,8 +96,6 @@ class TrackRepository(context: Context) {
// Merge: put stored tracks behind any in-memory tracks from this session
_pastTracks.addAll(stored)
NavLogger.i("track", "getPastTracks: total in memory=${_pastTracks.size}")
- } else {
- NavLogger.i("track", "getPastTracks: returning cached ${_pastTracks.size} tracks")
}
_pastTracks.toList()
}
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 c648fab..0dc1705 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
@@ -1,7 +1,9 @@
package org.terst.nav.ui
+import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
+import org.terst.nav.NavApplication
import org.terst.nav.ais.AisHubSource
import org.terst.nav.ais.AisRepository
import org.terst.nav.ais.AisVessel
@@ -37,8 +39,10 @@ import kotlinx.coroutines.launch
import org.terst.nav.track.TrackSummary
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
+import kotlin.math.asin
import kotlin.math.cos
import kotlin.math.sin
+import kotlin.math.sqrt
sealed class UiState {
object Loading : UiState()
@@ -87,9 +91,9 @@ class MainViewModel(
private val aisRepository = AisRepository()
- private val trackRepository = org.terst.nav.NavApplication.trackRepository
+ private val trackRepository = NavApplication.trackRepository
- val vesselRepository get() = org.terst.nav.NavApplication.vesselRepository
+ val vesselRepository get() = NavApplication.vesselRepository
init {
viewModelScope.launch {
@@ -240,30 +244,16 @@ class MainViewModel(
.create(AisHubApiService::class.java)
}
- private var lastConditionsLat = Double.NaN
- private var lastConditionsLon = Double.NaN
- private var lastConditionsMs = 0L
-
- private var lastWindGridLat = Double.NaN
- private var lastWindGridLon = Double.NaN
- private var lastWindGridMs = 0L
+ private val conditionsThrottle = GeoThrottle(minAgeMs = 20 * 60_000L, minDistNm = 10.0)
+ private val windGridThrottle = GeoThrottle(minAgeMs = 15 * 60_000L, minDistNm = 10.0)
/**
* Fetches current conditions snapshot for [lat]/[lon] and exposes it via [marineConditions].
* Throttled: skips unless the position moved >= 10 nm OR data is >= 20 min old.
*/
fun loadConditions(lat: Double, lon: Double) {
- val nowMs = System.currentTimeMillis()
- val ageMs = nowMs - lastConditionsMs
- val distNm = if (lastConditionsLat.isNaN()) Double.MAX_VALUE
- else distanceNm(lastConditionsLat, lastConditionsLon, lat, lon)
- if (ageMs < 20 * 60_000L && distNm < 10.0) {
- NavLogger.i("conditions", "skip age=${ageMs / 1000}s dist=%.1fnm".format(distNm))
- return
- }
- lastConditionsLat = lat
- lastConditionsLon = lon
- lastConditionsMs = nowMs
+ if (!conditionsThrottle.shouldFetch(lat, lon)) return
+ conditionsThrottle.update(lat, lon)
conditionsJob?.cancel()
conditionsJob = viewModelScope.launch {
@@ -273,7 +263,7 @@ class MainViewModel(
.onSuccess { _marineConditions.value = it }
.onFailure {
val msg = "${it.javaClass.simpleName}: ${it.message}"
- android.util.Log.e("Nav", "fetchCurrentConditions failed: $msg", it)
+ Log.e("Nav", "fetchCurrentConditions failed: $msg", it)
_conditionsError.emit(msg)
}
} finally {
@@ -288,7 +278,7 @@ class MainViewModel(
val a = sin(dLat / 2) * sin(dLat / 2) +
cos(Math.toRadians(lat1)) * cos(Math.toRadians(lat2)) *
sin(dLon / 2) * sin(dLon / 2)
- return 3440.065 * 2 * kotlin.math.asin(kotlin.math.sqrt(a))
+ return 3440.065 * 2 * asin(sqrt(a))
}
/**
@@ -330,14 +320,8 @@ class MainViewModel(
fun loadWindGrid(latMin: Double, latMax: Double, lonMin: Double, lonMax: Double) {
val centerLat = (latMin + latMax) / 2.0
val centerLon = (lonMin + lonMax) / 2.0
- val nowMs = System.currentTimeMillis()
- val ageMs = nowMs - lastWindGridMs
- val distNm = if (lastWindGridLat.isNaN()) Double.MAX_VALUE
- else distanceNm(lastWindGridLat, lastWindGridLon, centerLat, centerLon)
- if (ageMs < 15 * 60_000L && distNm < 10.0) return
- lastWindGridLat = centerLat
- lastWindGridLon = centerLon
- lastWindGridMs = nowMs
+ if (!windGridThrottle.shouldFetch(centerLat, centerLon)) return
+ windGridThrottle.update(centerLat, centerLon)
viewModelScope.launch {
val points = buildGrid(latMin, latMax, lonMin, lonMax, cols = 4, rows = 5)
repository.fetchWindGrid(points).getOrNull()?.let { _windGrid.value = it }
@@ -406,4 +390,20 @@ class MainViewModel(
}
}
}
+
+ private inner class GeoThrottle(private val minAgeMs: Long, private val minDistNm: Double) {
+ private var lastLat = Double.NaN
+ private var lastLon = Double.NaN
+ private var lastMs = 0L
+
+ fun shouldFetch(lat: Double, lon: Double): Boolean {
+ val dist = if (lastLat.isNaN()) Double.MAX_VALUE
+ else distanceNm(lastLat, lastLon, lat, lon)
+ return (System.currentTimeMillis() - lastMs) >= minAgeMs || dist >= minDistNm
+ }
+
+ fun update(lat: Double, lon: Double) {
+ lastLat = lat; lastLon = lon; lastMs = System.currentTimeMillis()
+ }
+ }
}
diff --git a/android-app/app/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt b/android-app/app/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt
index 7ebd2f6..faff0cb 100644
--- a/android-app/app/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt
+++ b/android-app/app/src/main/kotlin/org/terst/nav/vessel/VesselRepository.kt
@@ -23,10 +23,10 @@ class VesselRepository(context: Context) {
init {
prefs.getString(KEY_VESSELS, null)
?.let { runCatching { vesselAdapter.fromJson(it) }.getOrNull() }
- ?.forEach { vessels.add(it.toDomain()) }
+ ?.mapTo(vessels) { it.toDomain() }
prefs.getString(KEY_CREW, null)
?.let { runCatching { crewAdapter.fromJson(it) }.getOrNull() }
- ?.forEach { crew.add(it.toDomain()) }
+ ?.mapTo(crew) { it.toDomain() }
}
// --- Vessel CRUD ---