diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-05-07 08:58:57 -1000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-05-07 08:58:57 -1000 |
| commit | d8a53c3c9cba34dec7741395877a8dc87cebd0c5 (patch) | |
| tree | cb50ab2d4a0a0e443d9b42249420ece53319ec9c | |
| parent | 29fb98a20923b1b04a126ca00a2c21448ed822d5 (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>
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 --- |
