refactor(feature/node): Extract MetricsViewModel to commonMain

This commit is contained in:
James Rich 2026-03-16 11:03:01 -05:00
parent 8dd1113dfd
commit 52c2f6ef08
4 changed files with 41 additions and 123 deletions

View file

@ -16,6 +16,8 @@
*/
package org.meshtastic.feature.node.metrics
import org.meshtastic.core.common.util.toMeshtasticUri
import android.app.Activity
import android.content.Intent
import androidx.activity.compose.rememberLauncherForActivityResult
@ -119,7 +121,7 @@ fun PositionLogScreen(viewModel: MetricsViewModel, onNavigateUp: () -> Unit) {
val exportPositionLauncher =
rememberLauncherForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == Activity.RESULT_OK) {
it.data?.data?.let { uri -> viewModel.savePositionCSV(uri) }
it.data?.data?.let { uri -> viewModel.savePositionCSV(uri.toMeshtasticUri()) }
}
}

View file

@ -66,6 +66,13 @@ import org.meshtastic.proto.PortNum
import org.meshtastic.proto.Telemetry
import org.meshtastic.proto.Paxcount as ProtoPaxcount
import org.meshtastic.core.repository.FileService
import org.meshtastic.core.common.util.MeshtasticUri
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import okio.ByteString.Companion.decodeBase64
/**
* ViewModel responsible for managing and graphing metrics (telemetry, signal strength, paxcount) for a specific node.
*/
@ -81,6 +88,7 @@ open class MetricsViewModel(
private val nodeRequestActions: NodeRequestActions,
private val alertManager: AlertManager,
private val getNodeDetailsUseCase: GetNodeDetailsUseCase,
private val fileService: FileService,
) : ViewModel() {
private val nodeIdFromRoute: Int?
@ -315,8 +323,30 @@ open class MetricsViewModel(
Logger.d { "MetricsViewModel cleared" }
}
open fun savePositionCSV(uri: Any) {
// To be implemented in platform-specific subclass
fun savePositionCSV(uri: MeshtasticUri) {
viewModelScope.launch(dispatchers.main) {
val positions = state.value.positionLogs
fileService.write(uri) { sink ->
sink.writeUtf8("\"date\",\"time\",\"latitude\",\"longitude\",\"altitude\",\"satsInView\",\"speed\",\"heading\"\n")
positions.forEach { position ->
val localDateTime = Instant.fromEpochSeconds(position.time.toLong())
.toLocalDateTime(TimeZone.currentSystemDefault())
val rxDateTime = "\"${localDateTime.date}\",\"${localDateTime.time}\""
val latitude = (position.latitude_i ?: 0) * 1e-7
val longitude = (position.longitude_i ?: 0) * 1e-7
val altitude = position.altitude
val satsInView = position.sats_in_view
val speed = position.ground_speed
// Kotlin string format is available in common code on 1.9.20+ via String.format,
// but we can just do basic string manipulation if needed.
val heading = "%.2f".format((position.ground_track ?: 0) * 1e-5)
sink.writeUtf8("$rxDateTime,\"$latitude\",\"$longitude\",\"$altitude\",\"$satsInView\",\"$speed\",\"$heading\"\n")
}
}
}
}
@Suppress("MagicNumber", "CyclomaticComplexMethod", "ReturnCount")
@ -347,8 +377,7 @@ open class MetricsViewModel(
return null
}
protected open fun decodeBase64(base64: String): ByteArray {
// To be overridden in platform-specific subclass or use KMP library
return ByteArray(0)
protected fun decodeBase64(base64: String): ByteArray {
return base64.decodeBase64()?.toByteArray() ?: ByteArray(0)
}
}