Väg-flöde: vänta in och merga sammansättningsramen (muskler/fett/vatten)
All checks were successful
release / build-release (push) Successful in 5m38s
All checks were successful
release / build-release (push) Successful in 5m38s
Exingtech Y1/VScale skickar vikten först och kroppssammansättningen i en senare ram — tidigare låste skärmen resultatet och kopplade ner vid första publicerade mätningen, så bara vikten registrerades. Nu mergas ramarna (ScaleMeasurement.mergeWith) och anslutningen hålls öppen tills en ram med sammansättning kommit, max 20 s. UI visar 'väntar på muskler/fett/vatten' under tiden och en förklaring om sammansättningen uteblir (kroppsdata saknas / vägde med strumpor). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01C2GA94E1f3cmrvdQLQhYdg
This commit is contained in:
@@ -306,10 +306,19 @@ private fun DeviceRow(device: ScannedDeviceInfo, onClick: (() -> Unit)?) {
|
||||
sealed class WeighState {
|
||||
data object Idle : WeighState()
|
||||
data class Working(val status: String) : WeighState()
|
||||
data class Done(val measurement: ScaleMeasurement) : WeighState()
|
||||
|
||||
/**
|
||||
* Vikten är mottagen. [complete] = false medan vi fortfarande lyssnar efter
|
||||
* en senare ram med kroppssammansättning (vågen skickar ofta vikten först
|
||||
* och muskler/fett/vatten någon sekund senare).
|
||||
*/
|
||||
data class Done(val measurement: ScaleMeasurement, val complete: Boolean) : WeighState()
|
||||
data class Failed(val message: String) : WeighState()
|
||||
}
|
||||
|
||||
private fun ScaleMeasurement.hasComposition(): Boolean =
|
||||
fat > 0f || water > 0f || muscle > 0f
|
||||
|
||||
class WeighViewModel(
|
||||
private val scaleManager: ScaleManager,
|
||||
private val repo: GymRepository,
|
||||
@@ -319,31 +328,37 @@ class WeighViewModel(
|
||||
val saveError = MutableStateFlow<String?>(null)
|
||||
val missingBodyData = MutableStateFlow(false)
|
||||
|
||||
private var compositionTimeout: kotlinx.coroutines.Job? = null
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
scaleManager.lastEvent.collect { event ->
|
||||
// En färdig mätning ska inte skrivas över av senare events (t.ex. Disconnected)
|
||||
if (state.value is WeighState.Done) return@collect
|
||||
val current = state.value
|
||||
when (event) {
|
||||
null -> {}
|
||||
is BluetoothEvent.MeasurementReceived -> onMeasurement(event.measurement)
|
||||
is BluetoothEvent.Listening ->
|
||||
state.value = WeighState.Working("Lyssnar efter vågen — kliv på den…")
|
||||
if (current !is WeighState.Done)
|
||||
state.value = WeighState.Working("Lyssnar efter vågen — kliv på den…")
|
||||
is BluetoothEvent.Connected ->
|
||||
state.value = WeighState.Working("Ansluten — ställ dig barfota på vågen")
|
||||
if (current !is WeighState.Done)
|
||||
state.value = WeighState.Working("Ansluten — ställ dig barfota på vågen")
|
||||
is BluetoothEvent.DeviceMessage ->
|
||||
state.value = WeighState.Working(event.message)
|
||||
is BluetoothEvent.MeasurementReceived -> {
|
||||
state.value = WeighState.Done(event.measurement)
|
||||
scaleManager.disconnect()
|
||||
}
|
||||
if (current !is WeighState.Done)
|
||||
state.value = WeighState.Working(event.message)
|
||||
is BluetoothEvent.ConnectionFailed ->
|
||||
state.value = WeighState.Failed("Kunde inte ansluta: ${event.error}")
|
||||
if (current !is WeighState.Done)
|
||||
state.value = WeighState.Failed("Kunde inte ansluta: ${event.error}")
|
||||
is BluetoothEvent.Error ->
|
||||
state.value = WeighState.Failed(event.error)
|
||||
is BluetoothEvent.Disconnected -> {
|
||||
if (state.value is WeighState.Working) {
|
||||
if (current !is WeighState.Done)
|
||||
state.value = WeighState.Failed(event.error)
|
||||
is BluetoothEvent.Disconnected -> when (current) {
|
||||
is WeighState.Working ->
|
||||
state.value = WeighState.Failed("Vågen kopplade från innan mätningen blev klar")
|
||||
}
|
||||
// Tappade vi länken medan vi väntade på sammansättningen
|
||||
// behåller vi vikten och avslutar mätningen.
|
||||
is WeighState.Done -> finishMeasurement()
|
||||
else -> {}
|
||||
}
|
||||
is BluetoothEvent.BroadcastComplete, is BluetoothEvent.UserInteractionRequired -> {}
|
||||
}
|
||||
@@ -351,7 +366,44 @@ class WeighViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Vågen kan publicera flera ramar: först bara vikt, sen en med
|
||||
* muskler/fett/vatten. Vi mergar och blir klara först när sammansättningen
|
||||
* kommit — eller efter en väntetid om den uteblir.
|
||||
*/
|
||||
private fun onMeasurement(measurement: ScaleMeasurement) {
|
||||
val previous = (state.value as? WeighState.Done)?.measurement
|
||||
val merged = previous?.let { measurement.mergeWith(it) } ?: measurement
|
||||
|
||||
if (merged.hasComposition()) {
|
||||
compositionTimeout?.cancel()
|
||||
compositionTimeout = null
|
||||
state.value = WeighState.Done(merged, complete = true)
|
||||
scaleManager.disconnect()
|
||||
} else {
|
||||
state.value = WeighState.Done(merged, complete = false)
|
||||
if (compositionTimeout == null) {
|
||||
compositionTimeout = viewModelScope.launch {
|
||||
kotlinx.coroutines.delay(20_000)
|
||||
finishMeasurement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Avsluta med det vi har (vikt utan sammansättning). */
|
||||
private fun finishMeasurement() {
|
||||
compositionTimeout?.cancel()
|
||||
compositionTimeout = null
|
||||
(state.value as? WeighState.Done)?.let { d ->
|
||||
if (!d.complete) state.value = d.copy(complete = true)
|
||||
}
|
||||
scaleManager.disconnect()
|
||||
}
|
||||
|
||||
fun start() {
|
||||
compositionTimeout?.cancel()
|
||||
compositionTimeout = null
|
||||
state.value = WeighState.Working("Söker efter vågen…")
|
||||
saved.value = false
|
||||
saveError.value = null
|
||||
@@ -523,6 +575,29 @@ fun WeighScreen(
|
||||
ResultRow("Benmassa", m.bone, " kg")
|
||||
ResultRow("Bukfett (index)", m.visceralFat, "")
|
||||
|
||||
if (!s.complete) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CircularProgressIndicator(
|
||||
modifier = Modifier.size(16.dp),
|
||||
strokeWidth = 2.dp,
|
||||
)
|
||||
Text(
|
||||
"Väntar på muskler/fett/vatten — stå kvar på vågen…",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
modifier = Modifier.padding(start = 10.dp),
|
||||
)
|
||||
}
|
||||
} else if (!m.hasComposition()) {
|
||||
Text(
|
||||
"Ingen kroppssammansättning togs emot. Kontrollera att " +
|
||||
"längd/födelseår/kön är ifyllda i profilen och väg dig " +
|
||||
"barfota med torra fötter.",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
|
||||
saveError?.let {
|
||||
Text(
|
||||
it,
|
||||
|
||||
Reference in New Issue
Block a user