2024-11-26 08:38:12 -03:00
|
|
|
/*
|
|
|
|
|
* Copyright (c) 2024 Meshtastic LLC
|
|
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
|
2024-12-10 09:02:57 -06:00
|
|
|
@file:Suppress("TooManyFunctions", "LongMethod")
|
2024-10-19 12:35:07 -03:00
|
|
|
|
2024-10-18 19:27:15 -03:00
|
|
|
package com.geeksville.mesh.ui
|
|
|
|
|
|
2024-12-10 09:02:57 -06:00
|
|
|
import androidx.compose.foundation.Image
|
|
|
|
|
import androidx.compose.foundation.background
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.foundation.layout.Box
|
|
|
|
|
import androidx.compose.foundation.layout.Column
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
|
|
|
import androidx.compose.foundation.layout.FlowRow
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.foundation.layout.PaddingValues
|
|
|
|
|
import androidx.compose.foundation.layout.Row
|
|
|
|
|
import androidx.compose.foundation.layout.Spacer
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
|
|
import androidx.compose.foundation.layout.fillMaxWidth
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.foundation.layout.height
|
|
|
|
|
import androidx.compose.foundation.layout.heightIn
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.foundation.layout.padding
|
|
|
|
|
import androidx.compose.foundation.layout.size
|
|
|
|
|
import androidx.compose.foundation.layout.width
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.foundation.layout.widthIn
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.foundation.lazy.LazyColumn
|
2024-12-10 09:02:57 -06:00
|
|
|
import androidx.compose.foundation.shape.CircleShape
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
|
|
|
import androidx.compose.material.Card
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.CircularProgressIndicator
|
2024-12-10 12:58:05 -03:00
|
|
|
import androidx.compose.material.ContentAlpha
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.Icon
|
|
|
|
|
import androidx.compose.material.MaterialTheme
|
|
|
|
|
import androidx.compose.material.Text
|
|
|
|
|
import androidx.compose.material.icons.Icons
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.material.icons.filled.Air
|
|
|
|
|
import androidx.compose.material.icons.filled.BlurOn
|
|
|
|
|
import androidx.compose.material.icons.filled.Bolt
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.ChargingStation
|
|
|
|
|
import androidx.compose.material.icons.filled.CheckCircle
|
2024-11-23 07:16:35 -03:00
|
|
|
import androidx.compose.material.icons.filled.Height
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.History
|
|
|
|
|
import androidx.compose.material.icons.filled.KeyOff
|
2024-11-23 07:16:35 -03:00
|
|
|
import androidx.compose.material.icons.filled.LightMode
|
2024-11-02 13:23:04 -03:00
|
|
|
import androidx.compose.material.icons.filled.LocationOn
|
2024-11-06 11:00:38 -03:00
|
|
|
import androidx.compose.material.icons.filled.Map
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.Numbers
|
|
|
|
|
import androidx.compose.material.icons.filled.Person
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.material.icons.filled.Power
|
2024-10-25 08:14:32 -03:00
|
|
|
import androidx.compose.material.icons.filled.Route
|
|
|
|
|
import androidx.compose.material.icons.filled.Router
|
2024-11-23 07:16:35 -03:00
|
|
|
import androidx.compose.material.icons.filled.Scale
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.Settings
|
2024-10-23 13:31:31 -07:00
|
|
|
import androidx.compose.material.icons.filled.SignalCellularAlt
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.material.icons.filled.Speed
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.Thermostat
|
2024-12-10 09:02:57 -06:00
|
|
|
import androidx.compose.material.icons.filled.Verified
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.material.icons.filled.WaterDrop
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.material.icons.filled.Work
|
2024-11-23 07:16:35 -03:00
|
|
|
import androidx.compose.material.icons.outlined.Navigation
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.runtime.Composable
|
2024-10-29 07:40:18 -03:00
|
|
|
import androidx.compose.runtime.getValue
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.ui.Alignment
|
|
|
|
|
import androidx.compose.ui.Modifier
|
2024-12-10 09:02:57 -06:00
|
|
|
import androidx.compose.ui.draw.clip
|
2024-11-23 07:16:35 -03:00
|
|
|
import androidx.compose.ui.draw.rotate
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
|
|
|
import androidx.compose.ui.res.stringResource
|
2024-10-22 12:14:02 -03:00
|
|
|
import androidx.compose.ui.res.vectorResource
|
2024-12-10 12:58:05 -03:00
|
|
|
import androidx.compose.ui.text.style.TextAlign
|
2024-10-19 12:35:07 -03:00
|
|
|
import androidx.compose.ui.text.style.TextOverflow
|
2024-10-18 19:27:15 -03:00
|
|
|
import androidx.compose.ui.tooling.preview.Preview
|
|
|
|
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
|
|
|
|
import androidx.compose.ui.unit.dp
|
2024-10-29 07:40:18 -03:00
|
|
|
import androidx.hilt.navigation.compose.hiltViewModel
|
|
|
|
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
2024-11-23 07:16:35 -03:00
|
|
|
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
|
2024-10-18 19:27:15 -03:00
|
|
|
import com.geeksville.mesh.R
|
|
|
|
|
import com.geeksville.mesh.database.entity.NodeEntity
|
|
|
|
|
import com.geeksville.mesh.model.MetricsState
|
2024-10-29 07:40:18 -03:00
|
|
|
import com.geeksville.mesh.model.MetricsViewModel
|
2024-10-18 19:27:15 -03:00
|
|
|
import com.geeksville.mesh.ui.components.PreferenceCategory
|
|
|
|
|
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
|
|
|
|
|
import com.geeksville.mesh.ui.theme.AppTheme
|
2024-11-23 07:16:35 -03:00
|
|
|
import com.geeksville.mesh.util.DistanceUnit
|
2024-10-18 19:27:15 -03:00
|
|
|
import com.geeksville.mesh.util.formatAgo
|
2024-11-26 09:00:44 -03:00
|
|
|
import com.geeksville.mesh.util.formatUptime
|
2024-11-23 07:16:35 -03:00
|
|
|
import com.geeksville.mesh.util.thenIf
|
2024-10-19 12:35:07 -03:00
|
|
|
import kotlin.math.ln
|
2024-10-18 19:27:15 -03:00
|
|
|
|
|
|
|
|
@Composable
|
2024-10-29 07:40:18 -03:00
|
|
|
fun NodeDetailScreen(
|
2024-10-18 19:27:15 -03:00
|
|
|
modifier: Modifier = Modifier,
|
2024-12-10 09:02:57 -06:00
|
|
|
viewModel: MetricsViewModel = hiltViewModel(),
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate: (Any) -> Unit,
|
2024-10-18 19:27:15 -03:00
|
|
|
) {
|
2024-10-29 07:40:18 -03:00
|
|
|
val state by viewModel.state.collectAsStateWithLifecycle()
|
|
|
|
|
|
2024-11-30 07:50:15 -03:00
|
|
|
if (state.node != null) {
|
|
|
|
|
val node = state.node ?: return
|
2024-10-29 07:40:18 -03:00
|
|
|
NodeDetailList(
|
2024-10-18 19:27:15 -03:00
|
|
|
node = node,
|
2024-10-29 07:40:18 -03:00
|
|
|
metricsState = state,
|
2024-10-18 19:27:15 -03:00
|
|
|
onNavigate = onNavigate,
|
|
|
|
|
modifier = modifier,
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
Box(
|
|
|
|
|
modifier = Modifier.fillMaxSize(),
|
|
|
|
|
contentAlignment = Alignment.Center,
|
|
|
|
|
) {
|
|
|
|
|
CircularProgressIndicator()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
2024-10-29 07:40:18 -03:00
|
|
|
private fun NodeDetailList(
|
2024-12-10 09:02:57 -06:00
|
|
|
modifier: Modifier = Modifier,
|
2024-10-18 19:27:15 -03:00
|
|
|
node: NodeEntity,
|
|
|
|
|
metricsState: MetricsState,
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate: (Any) -> Unit = {},
|
2024-10-18 19:27:15 -03:00
|
|
|
) {
|
|
|
|
|
LazyColumn(
|
|
|
|
|
modifier = modifier.fillMaxSize(),
|
|
|
|
|
contentPadding = PaddingValues(horizontal = 16.dp),
|
|
|
|
|
) {
|
2024-12-22 03:56:40 -06:00
|
|
|
if (metricsState.deviceHardware != null) {
|
|
|
|
|
item {
|
|
|
|
|
PreferenceCategory("Device") {
|
|
|
|
|
DeviceDetailsContent(metricsState)
|
|
|
|
|
}
|
2024-12-10 09:02:57 -06:00
|
|
|
}
|
|
|
|
|
}
|
2024-10-18 19:27:15 -03:00
|
|
|
item {
|
|
|
|
|
PreferenceCategory("Details") {
|
2024-10-19 12:35:07 -03:00
|
|
|
NodeDetailsContent(node)
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-18 19:27:15 -03:00
|
|
|
|
2024-10-19 12:35:07 -03:00
|
|
|
if (node.hasEnvironmentMetrics) {
|
|
|
|
|
item {
|
|
|
|
|
PreferenceCategory("Environment")
|
2024-11-02 09:34:30 -03:00
|
|
|
EnvironmentMetrics(node, metricsState.isFahrenheit)
|
2024-10-19 12:35:07 -03:00
|
|
|
Spacer(modifier = Modifier.height(8.dp))
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-10-18 19:27:15 -03:00
|
|
|
|
2024-10-19 12:35:07 -03:00
|
|
|
if (node.hasPowerMetrics) {
|
|
|
|
|
item {
|
|
|
|
|
PreferenceCategory("Power")
|
|
|
|
|
PowerMetrics(node)
|
|
|
|
|
Spacer(modifier = Modifier.height(8.dp))
|
2024-10-18 19:27:15 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
item {
|
2024-11-06 11:00:38 -03:00
|
|
|
PreferenceCategory(stringResource(id = R.string.logs))
|
|
|
|
|
LogNavigationList(metricsState, onNavigate)
|
|
|
|
|
}
|
2024-10-25 08:14:32 -03:00
|
|
|
|
2024-11-06 11:00:38 -03:00
|
|
|
if (!metricsState.isManaged) {
|
|
|
|
|
item {
|
|
|
|
|
PreferenceCategory(stringResource(id = R.string.administration))
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(id = R.string.remote_admin),
|
|
|
|
|
icon = Icons.Default.Settings,
|
|
|
|
|
enabled = true
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.RadioConfig(node.num))
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
2024-10-18 19:27:15 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
2024-12-10 09:02:57 -06:00
|
|
|
private fun NodeDetailRow(
|
|
|
|
|
label: String,
|
|
|
|
|
icon: ImageVector,
|
|
|
|
|
value: String,
|
|
|
|
|
iconTint: Color = MaterialTheme.colors.onSurface
|
|
|
|
|
) {
|
2024-10-18 19:27:15 -03:00
|
|
|
Row(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.fillMaxWidth()
|
|
|
|
|
.padding(vertical = 8.dp),
|
2024-12-22 03:56:40 -06:00
|
|
|
verticalAlignment = Alignment.CenterVertically,
|
|
|
|
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
2024-10-18 19:27:15 -03:00
|
|
|
) {
|
|
|
|
|
Icon(
|
|
|
|
|
imageVector = icon,
|
|
|
|
|
contentDescription = label,
|
2024-12-10 09:02:57 -06:00
|
|
|
modifier = Modifier.size(24.dp),
|
|
|
|
|
tint = iconTint
|
2024-10-18 19:27:15 -03:00
|
|
|
)
|
|
|
|
|
Text(label)
|
|
|
|
|
Spacer(modifier = Modifier.weight(1f))
|
2024-12-22 03:56:40 -06:00
|
|
|
Text(textAlign = TextAlign.End, text = value)
|
2024-10-18 19:27:15 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-19 12:35:07 -03:00
|
|
|
@Composable
|
2024-12-10 09:02:57 -06:00
|
|
|
private fun DeviceDetailsContent(
|
|
|
|
|
state: MetricsState,
|
|
|
|
|
) {
|
|
|
|
|
val node = state.node ?: return
|
|
|
|
|
val deviceHardware = state.deviceHardware ?: return
|
|
|
|
|
val hwModelName = deviceHardware.displayName
|
|
|
|
|
val isSupported = deviceHardware.activelySupported
|
|
|
|
|
Box(
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.size(100.dp)
|
|
|
|
|
.padding(4.dp)
|
|
|
|
|
.clip(CircleShape)
|
|
|
|
|
.background(
|
|
|
|
|
color = Color(node.colors.second).copy(alpha = .5f),
|
|
|
|
|
shape = CircleShape
|
|
|
|
|
),
|
|
|
|
|
contentAlignment = Alignment.Center
|
|
|
|
|
) {
|
|
|
|
|
Image(
|
|
|
|
|
modifier = Modifier.padding(16.dp),
|
2024-12-10 12:19:33 -03:00
|
|
|
imageVector = ImageVector.vectorResource(deviceHardware.image),
|
2024-12-10 09:02:57 -06:00
|
|
|
contentDescription = hwModelName,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Hardware",
|
|
|
|
|
icon = Icons.Default.Router,
|
|
|
|
|
value = hwModelName
|
|
|
|
|
)
|
|
|
|
|
if (isSupported) {
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Supported",
|
|
|
|
|
icon = Icons.Default.Verified,
|
|
|
|
|
value = "",
|
|
|
|
|
iconTint = Color.Green
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Composable
|
|
|
|
|
private fun NodeDetailsContent(
|
|
|
|
|
node: NodeEntity,
|
|
|
|
|
) {
|
2024-10-19 12:35:07 -03:00
|
|
|
if (node.mismatchKey) {
|
|
|
|
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
|
|
|
Icon(
|
|
|
|
|
imageVector = Icons.Default.KeyOff,
|
|
|
|
|
contentDescription = stringResource(id = R.string.encryption_error),
|
|
|
|
|
tint = Color.Red,
|
|
|
|
|
)
|
2024-12-10 12:58:05 -03:00
|
|
|
Spacer(Modifier.width(12.dp))
|
|
|
|
|
Text(
|
|
|
|
|
text = stringResource(id = R.string.encryption_error),
|
|
|
|
|
style = MaterialTheme.typography.h6.copy(color = Color.Red),
|
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
|
)
|
2024-10-19 12:35:07 -03:00
|
|
|
}
|
2024-12-10 12:58:05 -03:00
|
|
|
Spacer(Modifier.height(16.dp))
|
|
|
|
|
Text(
|
|
|
|
|
text = stringResource(id = R.string.encryption_error_text),
|
|
|
|
|
style = MaterialTheme.typography.body2,
|
|
|
|
|
color = MaterialTheme.colors.onSurface.copy(alpha = ContentAlpha.medium),
|
|
|
|
|
textAlign = TextAlign.Center,
|
|
|
|
|
)
|
|
|
|
|
Spacer(Modifier.height(16.dp))
|
2024-10-19 12:35:07 -03:00
|
|
|
}
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Node Number",
|
|
|
|
|
icon = Icons.Default.Numbers,
|
|
|
|
|
value = node.num.toUInt().toString()
|
|
|
|
|
)
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "User Id",
|
|
|
|
|
icon = Icons.Default.Person,
|
|
|
|
|
value = node.user.id
|
|
|
|
|
)
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Role",
|
|
|
|
|
icon = Icons.Default.Work,
|
|
|
|
|
value = node.user.role.name
|
|
|
|
|
)
|
|
|
|
|
if (node.deviceMetrics.uptimeSeconds > 0) {
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Uptime",
|
|
|
|
|
icon = Icons.Default.CheckCircle,
|
|
|
|
|
value = formatUptime(node.deviceMetrics.uptimeSeconds)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
NodeDetailRow(
|
|
|
|
|
label = "Last heard",
|
|
|
|
|
icon = Icons.Default.History,
|
|
|
|
|
value = formatAgo(node.lastHeard)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-06 11:00:38 -03:00
|
|
|
@Composable
|
2024-11-09 06:23:40 -03:00
|
|
|
fun LogNavigationList(state: MetricsState, onNavigate: (Any) -> Unit) {
|
2024-11-06 11:00:38 -03:00
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.device_metrics_log),
|
|
|
|
|
icon = Icons.Default.ChargingStation,
|
|
|
|
|
enabled = state.hasDeviceMetrics()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.DeviceMetrics)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.node_map),
|
|
|
|
|
icon = Icons.Default.Map,
|
|
|
|
|
enabled = state.hasPositionLogs()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.NodeMap)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.position_log),
|
|
|
|
|
icon = Icons.Default.LocationOn,
|
|
|
|
|
enabled = state.hasPositionLogs()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.PositionLog)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.env_metrics_log),
|
|
|
|
|
icon = Icons.Default.Thermostat,
|
|
|
|
|
enabled = state.hasEnvironmentMetrics()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.EnvironmentMetrics)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.sig_metrics_log),
|
|
|
|
|
icon = Icons.Default.SignalCellularAlt,
|
|
|
|
|
enabled = state.hasSignalMetrics()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.SignalMetrics)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NavCard(
|
|
|
|
|
title = stringResource(R.string.traceroute_log),
|
|
|
|
|
icon = Icons.Default.Route,
|
|
|
|
|
enabled = state.hasTracerouteLogs()
|
|
|
|
|
) {
|
2024-11-09 06:23:40 -03:00
|
|
|
onNavigate(Route.TracerouteLog)
|
2024-11-06 11:00:38 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-19 12:35:07 -03:00
|
|
|
@Composable
|
|
|
|
|
private fun InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon: ImageVector,
|
2024-10-19 12:35:07 -03:00
|
|
|
text: String,
|
|
|
|
|
value: String,
|
2024-11-23 07:16:35 -03:00
|
|
|
rotateIcon: Float = 0f,
|
2024-10-19 12:35:07 -03:00
|
|
|
) {
|
|
|
|
|
Card(
|
|
|
|
|
shape = RoundedCornerShape(12.dp),
|
|
|
|
|
backgroundColor = MaterialTheme.colors.surface,
|
|
|
|
|
elevation = 4.dp,
|
|
|
|
|
modifier = Modifier
|
|
|
|
|
.padding(4.dp)
|
|
|
|
|
.widthIn(min = 100.dp, max = 150.dp)
|
|
|
|
|
.heightIn(min = 100.dp, max = 150.dp)
|
|
|
|
|
) {
|
|
|
|
|
Column(
|
|
|
|
|
verticalArrangement = Arrangement.Center,
|
|
|
|
|
horizontalAlignment = Alignment.CenterHorizontally,
|
|
|
|
|
) {
|
|
|
|
|
Icon(
|
2024-10-22 12:14:02 -03:00
|
|
|
imageVector = icon,
|
2024-10-19 12:35:07 -03:00
|
|
|
contentDescription = text,
|
2024-11-23 07:16:35 -03:00
|
|
|
modifier = Modifier
|
|
|
|
|
.size(24.dp)
|
|
|
|
|
.thenIf(rotateIcon != 0f) { rotate(rotateIcon) },
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
Text(
|
|
|
|
|
text = text,
|
|
|
|
|
maxLines = 1,
|
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
|
|
|
|
style = MaterialTheme.typography.subtitle2
|
|
|
|
|
)
|
|
|
|
|
Text(
|
|
|
|
|
text = value,
|
|
|
|
|
maxLines = 1,
|
|
|
|
|
overflow = TextOverflow.Ellipsis,
|
2024-11-23 07:16:35 -03:00
|
|
|
style = if (value.length < 7) {
|
|
|
|
|
MaterialTheme.typography.h5
|
|
|
|
|
} else {
|
|
|
|
|
MaterialTheme.typography.h6
|
|
|
|
|
},
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 06:39:27 -03:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2024-11-23 07:16:35 -03:00
|
|
|
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
2024-10-19 12:35:07 -03:00
|
|
|
@Composable
|
|
|
|
|
private fun EnvironmentMetrics(
|
|
|
|
|
node: NodeEntity,
|
|
|
|
|
isFahrenheit: Boolean = false,
|
|
|
|
|
) = with(node.environmentMetrics) {
|
2024-10-20 06:39:27 -03:00
|
|
|
FlowRow(
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
verticalArrangement = Arrangement.SpaceEvenly,
|
|
|
|
|
) {
|
|
|
|
|
if (temperature != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Thermostat,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Temperature",
|
|
|
|
|
value = temperature.toTempString(isFahrenheit)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (relativeHumidity != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.WaterDrop,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Humidity",
|
|
|
|
|
value = "%.0f%%".format(relativeHumidity)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (temperature != 0f && relativeHumidity != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
val dewPoint = calculateDewPoint(temperature, relativeHumidity)
|
|
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = ImageVector.vectorResource(R.drawable.ic_outlined_dew_point_24),
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Dew Point",
|
|
|
|
|
value = dewPoint.toTempString(isFahrenheit)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (barometricPressure != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Speed,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Pressure",
|
|
|
|
|
value = "%.0f".format(barometricPressure)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (gasResistance != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.BlurOn,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Gas Resistance",
|
|
|
|
|
value = "%.0f".format(gasResistance)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (voltage != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Bolt,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Voltage",
|
2024-11-03 11:59:09 -03:00
|
|
|
value = "%.2fV".format(voltage)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (current != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Power,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "Current",
|
2024-10-20 06:39:27 -03:00
|
|
|
value = "%.1fmA".format(current)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (iaq != 0) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Air,
|
2024-10-19 12:35:07 -03:00
|
|
|
text = "IAQ",
|
|
|
|
|
value = iaq.toString()
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-11-23 07:16:35 -03:00
|
|
|
if (distance != 0f) {
|
|
|
|
|
InfoCard(
|
|
|
|
|
icon = Icons.Default.Height,
|
|
|
|
|
text = "Distance",
|
|
|
|
|
value = "%.0f mm".format(distance)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (lux != 0f) {
|
|
|
|
|
InfoCard(
|
|
|
|
|
icon = Icons.Default.LightMode,
|
|
|
|
|
text = "Lux",
|
|
|
|
|
value = "%.0f".format(lux)
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (hasWindSpeed()) {
|
|
|
|
|
@Suppress("MagicNumber")
|
|
|
|
|
val normalizedBearing = (windDirection % 360 + 360) % 360
|
|
|
|
|
InfoCard(
|
|
|
|
|
icon = Icons.Outlined.Navigation,
|
|
|
|
|
text = "Wind",
|
|
|
|
|
value = windSpeed.toSpeedString(),
|
|
|
|
|
rotateIcon = normalizedBearing.toFloat(),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
if (weight != 0f) {
|
|
|
|
|
InfoCard(
|
|
|
|
|
icon = Icons.Default.Scale,
|
|
|
|
|
text = "Weight",
|
|
|
|
|
value = "%.2f kg".format(weight)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-12-05 19:39:37 -03:00
|
|
|
if (radiation != 0f) {
|
|
|
|
|
InfoCard(
|
|
|
|
|
icon = ImageVector.vectorResource(R.drawable.ic_filled_radioactive_24),
|
|
|
|
|
text = "Radiation",
|
|
|
|
|
value = "%.1f µR".format(radiation)
|
|
|
|
|
)
|
|
|
|
|
}
|
2024-10-19 12:35:07 -03:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Suppress("MagicNumber")
|
|
|
|
|
private fun Float.toTempString(isFahrenheit: Boolean) = if (isFahrenheit) {
|
|
|
|
|
val fahrenheit = this * 1.8F + 32
|
|
|
|
|
"%.0f°F".format(fahrenheit)
|
|
|
|
|
} else {
|
|
|
|
|
"%.0f°C".format(this)
|
|
|
|
|
}
|
|
|
|
|
|
2024-11-23 07:16:35 -03:00
|
|
|
@Suppress("MagicNumber")
|
|
|
|
|
private fun Float.toSpeedString() = when (DistanceUnit.getFromLocale()) {
|
|
|
|
|
DisplayUnits.METRIC -> "%.0f km/h".format(this * 3.6)
|
|
|
|
|
else -> "%.0f mph".format(this * 2.23694f)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-19 12:35:07 -03:00
|
|
|
// Magnus-Tetens approximation
|
|
|
|
|
@Suppress("MagicNumber")
|
|
|
|
|
private fun calculateDewPoint(tempCelsius: Float, humidity: Float): Float {
|
|
|
|
|
val (a, b) = 17.27f to 237.7f
|
|
|
|
|
val alpha = (a * tempCelsius) / (b + tempCelsius) + ln(humidity / 100f)
|
|
|
|
|
return (b * alpha) / (a - alpha)
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-20 06:39:27 -03:00
|
|
|
@OptIn(ExperimentalLayoutApi::class)
|
2024-10-19 12:35:07 -03:00
|
|
|
@Composable
|
|
|
|
|
private fun PowerMetrics(node: NodeEntity) = with(node.powerMetrics) {
|
2024-10-20 06:39:27 -03:00
|
|
|
FlowRow(
|
|
|
|
|
modifier = Modifier.fillMaxWidth(),
|
|
|
|
|
horizontalArrangement = Arrangement.SpaceBetween,
|
|
|
|
|
verticalArrangement = Arrangement.SpaceEvenly,
|
|
|
|
|
) {
|
|
|
|
|
if (ch1Voltage != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Bolt,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 1",
|
2024-11-03 11:59:09 -03:00
|
|
|
value = "%.2fV".format(ch1Voltage)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (ch1Current != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Power,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 1",
|
|
|
|
|
value = "%.1fmA".format(ch1Current)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (ch2Voltage != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Bolt,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 2",
|
2024-11-03 11:59:09 -03:00
|
|
|
value = "%.2fV".format(ch2Voltage)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (ch2Current != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Power,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 2",
|
|
|
|
|
value = "%.1fmA".format(ch2Current)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (ch3Voltage != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Bolt,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 3",
|
2024-11-03 11:59:09 -03:00
|
|
|
value = "%.2fV".format(ch3Voltage)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
2024-10-20 06:39:27 -03:00
|
|
|
if (ch3Current != 0f) {
|
2024-10-19 12:35:07 -03:00
|
|
|
InfoCard(
|
2024-10-22 12:14:02 -03:00
|
|
|
icon = Icons.Default.Power,
|
2024-10-20 06:39:27 -03:00
|
|
|
text = "Channel 3",
|
|
|
|
|
value = "%.1fmA".format(ch3Current)
|
2024-10-19 12:35:07 -03:00
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-18 19:27:15 -03:00
|
|
|
@Preview(showBackground = true)
|
|
|
|
|
@Composable
|
|
|
|
|
private fun NodeDetailsPreview(
|
|
|
|
|
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
|
|
|
|
|
node: NodeEntity
|
|
|
|
|
) {
|
|
|
|
|
AppTheme {
|
2024-12-10 09:02:57 -06:00
|
|
|
NodeDetailList(
|
|
|
|
|
node = node,
|
|
|
|
|
metricsState = MetricsState.Empty,
|
|
|
|
|
)
|
2024-10-18 19:27:15 -03:00
|
|
|
}
|
|
|
|
|
}
|