mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat(config): implement excluded modules validation (#1460)
* feat(config): implement excluded modules validation * feat: hide excluded configs from metadata * refactor: save local metadata from WantConfig * refactor: delete metadata from deleted nodes * fix: always request metadata for admin routes * feat: show node firmware when metadata is available * refactor: rename filter function * feat: add `ServiceAction` request metadata
This commit is contained in:
parent
bdefbc3ce2
commit
60e7e18116
28 changed files with 1164 additions and 358 deletions
|
|
@ -71,6 +71,7 @@ import androidx.navigation.NavHostController
|
|||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.geeksville.mesh.MeshProtos.DeviceMetadata
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
|
|
@ -235,6 +236,18 @@ enum class ConfigRoute(val title: String, val route: Route, val icon: ImageVecto
|
|||
LORA("LoRa", Route.LoRa, Icons.Default.CellTower, 5),
|
||||
BLUETOOTH("Bluetooth", Route.Bluetooth, Icons.Default.Bluetooth, 6),
|
||||
SECURITY("Security", Route.Security, Icons.Default.Security, type = 7),
|
||||
;
|
||||
|
||||
companion object {
|
||||
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ConfigRoute> = entries.filter {
|
||||
when {
|
||||
metadata == null -> true
|
||||
it == BLUETOOTH -> metadata.hasBluetooth
|
||||
it == NETWORK -> metadata.hasWifi || metadata.hasEthernet
|
||||
else -> true // Include all other routes by default
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ModuleConfig (type = AdminProtos.AdminMessage.ModuleConfigType)
|
||||
|
|
@ -252,6 +265,18 @@ enum class ModuleRoute(val title: String, val route: Route, val icon: ImageVecto
|
|||
AMBIENT_LIGHTING("Ambient Lighting", Route.AmbientLighting, Icons.Default.LightMode, 10),
|
||||
DETECTION_SENSOR("Detection Sensor", Route.DetectionSensor, Icons.Default.Sensors, 11),
|
||||
PAXCOUNTER("Paxcounter", Route.Paxcounter, Icons.Default.PermScanWifi, 12),
|
||||
;
|
||||
|
||||
val bitfield: Int get() = 1 shl ordinal
|
||||
|
||||
companion object {
|
||||
fun filterExcludedFrom(metadata: DeviceMetadata?): List<ModuleRoute> = entries.filter {
|
||||
when (metadata) {
|
||||
null -> true
|
||||
else -> metadata.excludedModules and it.bitfield == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import androidx.compose.material.icons.filled.KeyOff
|
|||
import androidx.compose.material.icons.filled.LightMode
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.Map
|
||||
import androidx.compose.material.icons.filled.Memory
|
||||
import androidx.compose.material.icons.filled.Numbers
|
||||
import androidx.compose.material.icons.filled.Person
|
||||
import androidx.compose.material.icons.filled.Power
|
||||
|
|
@ -91,11 +92,11 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.MetricsState
|
||||
import com.geeksville.mesh.model.MetricsViewModel
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
import com.geeksville.mesh.util.DistanceUnit
|
||||
import com.geeksville.mesh.util.formatAgo
|
||||
|
|
@ -132,7 +133,7 @@ fun NodeDetailScreen(
|
|||
@Composable
|
||||
private fun NodeDetailList(
|
||||
modifier: Modifier = Modifier,
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
metricsState: MetricsState,
|
||||
onNavigate: (Any) -> Unit = {},
|
||||
) {
|
||||
|
|
@ -257,7 +258,7 @@ private fun DeviceDetailsContent(
|
|||
|
||||
@Composable
|
||||
private fun NodeDetailsContent(
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
) {
|
||||
if (node.mismatchKey) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
|
|
@ -304,6 +305,13 @@ private fun NodeDetailsContent(
|
|||
value = formatUptime(node.deviceMetrics.uptimeSeconds)
|
||||
)
|
||||
}
|
||||
if (node.metadata != null) {
|
||||
NodeDetailRow(
|
||||
label = "Firmware version",
|
||||
icon = Icons.Default.Memory,
|
||||
value = node.metadata.firmwareVersion.substringBeforeLast(".")
|
||||
)
|
||||
}
|
||||
NodeDetailRow(
|
||||
label = "Last heard",
|
||||
icon = Icons.Default.History,
|
||||
|
|
@ -413,7 +421,7 @@ private fun InfoCard(
|
|||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
private fun EnvironmentMetrics(
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
isFahrenheit: Boolean = false,
|
||||
) = with(node.environmentMetrics) {
|
||||
FlowRow(
|
||||
|
|
@ -543,7 +551,7 @@ private fun calculateDewPoint(tempCelsius: Float, humidity: Float): Float {
|
|||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun PowerMetrics(node: NodeEntity) = with(node.powerMetrics) {
|
||||
private fun PowerMetrics(node: Node) = with(node.powerMetrics) {
|
||||
FlowRow(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
|
|
@ -597,8 +605,8 @@ private fun PowerMetrics(node: NodeEntity) = with(node.powerMetrics) {
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun NodeDetailsPreview(
|
||||
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
|
||||
node: NodeEntity
|
||||
@PreviewParameter(NodePreviewParameterProvider::class)
|
||||
node: Node
|
||||
) {
|
||||
AppTheme {
|
||||
NodeDetailList(
|
||||
|
|
|
|||
|
|
@ -59,14 +59,14 @@ import com.geeksville.mesh.ConfigProtos.Config.DeviceConfig
|
|||
import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.ui.components.NodeMenuAction
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.components.NodeKeyStatusIcon
|
||||
import com.geeksville.mesh.ui.components.NodeMenu
|
||||
import com.geeksville.mesh.ui.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.components.SignalInfo
|
||||
import com.geeksville.mesh.ui.compose.ElevationInfo
|
||||
import com.geeksville.mesh.ui.compose.SatelliteCountInfo
|
||||
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
import com.geeksville.mesh.util.toDistanceString
|
||||
|
||||
|
|
@ -74,8 +74,8 @@ import com.geeksville.mesh.util.toDistanceString
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun NodeItem(
|
||||
thisNode: NodeEntity?,
|
||||
thatNode: NodeEntity,
|
||||
thisNode: Node?,
|
||||
thatNode: Node,
|
||||
gpsFormat: Int,
|
||||
distanceUnits: Int,
|
||||
tempInFahrenheit: Boolean,
|
||||
|
|
@ -293,8 +293,8 @@ fun NodeItem(
|
|||
@Preview(showBackground = false)
|
||||
fun NodeInfoSimplePreview() {
|
||||
AppTheme {
|
||||
val thisNode = NodeEntityPreviewParameterProvider().values.first()
|
||||
val thatNode = NodeEntityPreviewParameterProvider().values.last()
|
||||
val thisNode = NodePreviewParameterProvider().values.first()
|
||||
val thatNode = NodePreviewParameterProvider().values.last()
|
||||
NodeItem(
|
||||
thisNode = thisNode,
|
||||
thatNode = thatNode,
|
||||
|
|
@ -312,11 +312,11 @@ fun NodeInfoSimplePreview() {
|
|||
uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES,
|
||||
)
|
||||
fun NodeInfoPreview(
|
||||
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
|
||||
thatNode: NodeEntity
|
||||
@PreviewParameter(NodePreviewParameterProvider::class)
|
||||
thatNode: Node
|
||||
) {
|
||||
AppTheme {
|
||||
val thisNode = NodeEntityPreviewParameterProvider().values.first()
|
||||
val thisNode = NodePreviewParameterProvider().values.first()
|
||||
Column {
|
||||
Text(
|
||||
text = "Details Collapsed",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.model.RadioConfigState
|
||||
import com.geeksville.mesh.model.RadioConfigViewModel
|
||||
import com.geeksville.mesh.ui.components.PreferenceCategory
|
||||
import com.geeksville.mesh.ui.components.config.EditDeviceProfileDialog
|
||||
|
|
@ -150,8 +151,7 @@ fun RadioConfigScreen(
|
|||
}
|
||||
|
||||
RadioConfigItemList(
|
||||
enabled = state.connected && !isWaiting,
|
||||
isLocal = state.isLocal,
|
||||
state = state,
|
||||
modifier = modifier,
|
||||
onRouteClick = { route ->
|
||||
isWaiting = true
|
||||
|
|
@ -285,28 +285,28 @@ private fun NavButton(@StringRes title: Int, enabled: Boolean, onClick: () -> Un
|
|||
|
||||
@Composable
|
||||
private fun RadioConfigItemList(
|
||||
enabled: Boolean = true,
|
||||
isLocal: Boolean = true,
|
||||
state: RadioConfigState,
|
||||
modifier: Modifier = Modifier,
|
||||
onRouteClick: (Enum<*>) -> Unit = {},
|
||||
onImport: () -> Unit = {},
|
||||
onExport: () -> Unit = {},
|
||||
) {
|
||||
val enabled = state.connected && !state.responseState.isWaiting()
|
||||
LazyColumn(
|
||||
modifier = modifier,
|
||||
contentPadding = PaddingValues(horizontal = 16.dp),
|
||||
) {
|
||||
item { PreferenceCategory(stringResource(R.string.device_settings)) }
|
||||
items(ConfigRoute.entries) {
|
||||
items(ConfigRoute.filterExcludedFrom(state.metadata)) {
|
||||
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
|
||||
}
|
||||
|
||||
item { PreferenceCategory(stringResource(R.string.module_settings)) }
|
||||
items(ModuleRoute.entries) {
|
||||
items(ModuleRoute.filterExcludedFrom(state.metadata)) {
|
||||
NavCard(title = it.title, icon = it.icon, enabled = enabled) { onRouteClick(it) }
|
||||
}
|
||||
|
||||
if (isLocal) {
|
||||
if (state.isLocal) {
|
||||
item {
|
||||
PreferenceCategory("Backup & Restore")
|
||||
NavCard(
|
||||
|
|
@ -331,5 +331,7 @@ private fun RadioConfigItemList(
|
|||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun RadioSettingsScreenPreview() {
|
||||
RadioConfigItemList()
|
||||
RadioConfigItemList(
|
||||
RadioConfigState(isLocal = true, connected = true)
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.ui.components.NodeMenuAction
|
||||
import com.geeksville.mesh.ui.components.NodeFilterTextField
|
||||
|
|
@ -53,7 +53,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
|
|||
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
private fun navigateToMessages(node: NodeEntity) = node.user.let { user ->
|
||||
private fun navigateToMessages(node: Node) = node.user.let { user ->
|
||||
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC // TODO use meta.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
val contactKey = "$channel${user.id}"
|
||||
|
|
@ -91,7 +91,7 @@ class UsersFragment : ScreenFragment("Users"), Logging {
|
|||
@Suppress("LongMethod")
|
||||
fun NodesScreen(
|
||||
model: UIViewModel = hiltViewModel(),
|
||||
navigateToMessages: (NodeEntity) -> Unit,
|
||||
navigateToMessages: (Node) -> Unit,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
) {
|
||||
val state by model.nodesUiState.collectAsStateWithLifecycle()
|
||||
|
|
|
|||
|
|
@ -36,12 +36,12 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun NodeMenu(
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
showFullMenu: Boolean = false,
|
||||
onDismissRequest: () -> Unit,
|
||||
expanded: Boolean = false,
|
||||
|
|
@ -150,11 +150,11 @@ fun NodeMenu(
|
|||
}
|
||||
|
||||
sealed class NodeMenuAction {
|
||||
data class Remove(val node: NodeEntity) : NodeMenuAction()
|
||||
data class Ignore(val node: NodeEntity) : NodeMenuAction()
|
||||
data class DirectMessage(val node: NodeEntity) : NodeMenuAction()
|
||||
data class RequestUserInfo(val node: NodeEntity) : NodeMenuAction()
|
||||
data class RequestPosition(val node: NodeEntity) : NodeMenuAction()
|
||||
data class TraceRoute(val node: NodeEntity) : NodeMenuAction()
|
||||
data class MoreDetails(val node: NodeEntity) : NodeMenuAction()
|
||||
data class Remove(val node: Node) : NodeMenuAction()
|
||||
data class Ignore(val node: Node) : NodeMenuAction()
|
||||
data class DirectMessage(val node: Node) : NodeMenuAction()
|
||||
data class RequestUserInfo(val node: Node) : NodeMenuAction()
|
||||
data class RequestPosition(val node: Node) : NodeMenuAction()
|
||||
data class TraceRoute(val node: Node) : NodeMenuAction()
|
||||
data class MoreDetails(val node: Node) : NodeMenuAction()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
const val MAX_VALID_SNR = 100F
|
||||
|
|
@ -36,7 +36,7 @@ const val MAX_VALID_RSSI = 0
|
|||
@Composable
|
||||
fun SignalInfo(
|
||||
modifier: Modifier = Modifier,
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
isThisNode: Boolean
|
||||
) {
|
||||
val text = if (isThisNode) {
|
||||
|
|
@ -81,7 +81,7 @@ fun SignalInfo(
|
|||
fun SignalInfoSimplePreview() {
|
||||
AppTheme {
|
||||
SignalInfo(
|
||||
node = NodeEntity(
|
||||
node = Node(
|
||||
num = 1,
|
||||
lastHeard = 0,
|
||||
channel = 0,
|
||||
|
|
@ -97,8 +97,8 @@ fun SignalInfoSimplePreview() {
|
|||
@PreviewLightDark
|
||||
@Composable
|
||||
fun SignalInfoPreview(
|
||||
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
|
||||
node: NodeEntity
|
||||
@PreviewParameter(NodePreviewParameterProvider::class)
|
||||
node: Node
|
||||
) {
|
||||
AppTheme {
|
||||
SignalInfo(
|
||||
|
|
@ -111,8 +111,8 @@ fun SignalInfoPreview(
|
|||
@Composable
|
||||
@PreviewLightDark
|
||||
fun SignalInfoSelfPreview(
|
||||
@PreviewParameter(NodeEntityPreviewParameterProvider::class)
|
||||
node: NodeEntity
|
||||
@PreviewParameter(NodePreviewParameterProvider::class)
|
||||
node: Node
|
||||
) {
|
||||
AppTheme {
|
||||
SignalInfo(
|
||||
|
|
|
|||
|
|
@ -81,6 +81,8 @@ fun NetworkConfigScreen(
|
|||
}
|
||||
|
||||
NetworkConfigItemList(
|
||||
hasWifi = state.metadata?.hasWifi ?: true,
|
||||
hasEthernet = state.metadata?.hasEthernet ?: true,
|
||||
networkConfig = state.radioConfig.network,
|
||||
enabled = state.connected,
|
||||
onSaveClicked = { networkInput ->
|
||||
|
|
@ -94,8 +96,11 @@ private fun extractWifiCredentials(qrCode: String) = Regex("""WIFI:S:(.*?);.*?P:
|
|||
.find(qrCode)?.destructured
|
||||
?.let { (ssid, password) -> ssid to password } ?: (null to null)
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@Composable
|
||||
fun NetworkConfigItemList(
|
||||
hasWifi: Boolean,
|
||||
hasEthernet: Boolean,
|
||||
networkConfig: NetworkConfig,
|
||||
enabled: Boolean,
|
||||
onSaveClicked: (NetworkConfig) -> Unit,
|
||||
|
|
@ -137,16 +142,16 @@ fun NetworkConfigItemList(
|
|||
item {
|
||||
SwitchPreference(title = "WiFi enabled",
|
||||
checked = networkInput.wifiEnabled,
|
||||
enabled = enabled,
|
||||
enabled = enabled && hasWifi,
|
||||
onCheckedChange = { networkInput = networkInput.copy { wifiEnabled = it } })
|
||||
Divider()
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "SSID",
|
||||
value = networkInput.wifiSsid,
|
||||
maxSize = 32, // wifi_ssid max_size:33
|
||||
enabled = enabled,
|
||||
enabled = enabled && hasWifi,
|
||||
isError = false,
|
||||
keyboardOptions = KeyboardOptions.Default.copy(
|
||||
keyboardType = KeyboardType.Text, imeAction = ImeAction.Done
|
||||
|
|
@ -161,7 +166,7 @@ fun NetworkConfigItemList(
|
|||
EditPasswordPreference(title = "PSK",
|
||||
value = networkInput.wifiPsk,
|
||||
maxSize = 64, // wifi_psk max_size:65
|
||||
enabled = enabled,
|
||||
enabled = enabled && hasWifi,
|
||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }),
|
||||
onValueChanged = { networkInput = networkInput.copy { wifiPsk = it } })
|
||||
}
|
||||
|
|
@ -173,12 +178,20 @@ fun NetworkConfigItemList(
|
|||
.fillMaxWidth()
|
||||
.padding(vertical = 8.dp)
|
||||
.height(48.dp),
|
||||
enabled = enabled,
|
||||
enabled = enabled && hasWifi,
|
||||
) {
|
||||
Text(text = stringResource(R.string.wifi_qr_code_scan))
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Ethernet enabled",
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled && hasEthernet,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } })
|
||||
Divider()
|
||||
}
|
||||
|
||||
item {
|
||||
EditTextPreference(title = "NTP server",
|
||||
value = networkInput.ntpServer,
|
||||
|
|
@ -209,14 +222,6 @@ fun NetworkConfigItemList(
|
|||
})
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchPreference(title = "Ethernet enabled",
|
||||
checked = networkInput.ethEnabled,
|
||||
enabled = enabled,
|
||||
onCheckedChange = { networkInput = networkInput.copy { ethEnabled = it } })
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
DropDownPreference(title = "IPv4 mode",
|
||||
enabled = enabled,
|
||||
|
|
@ -225,8 +230,8 @@ fun NetworkConfigItemList(
|
|||
.map { it to it.name },
|
||||
selectedItem = networkInput.addressMode,
|
||||
onItemSelected = { networkInput = networkInput.copy { addressMode = it } })
|
||||
Divider()
|
||||
}
|
||||
item { Divider() }
|
||||
|
||||
item {
|
||||
EditIPv4Preference(title = "IP",
|
||||
|
|
@ -292,6 +297,8 @@ fun NetworkConfigItemList(
|
|||
@Composable
|
||||
private fun NetworkConfigPreview() {
|
||||
NetworkConfigItemList(
|
||||
hasWifi = true,
|
||||
hasEthernet = true,
|
||||
networkConfig = NetworkConfig.getDefaultInstance(),
|
||||
enabled = true,
|
||||
onSaveClicked = { },
|
||||
|
|
|
|||
|
|
@ -65,8 +65,8 @@ import com.geeksville.mesh.android.gpsDisabled
|
|||
import com.geeksville.mesh.android.hasGps
|
||||
import com.geeksville.mesh.android.hasLocationPermission
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.model.map.CustomTileSource
|
||||
import com.geeksville.mesh.model.map.MarkerWithLabel
|
||||
|
|
@ -311,7 +311,7 @@ fun MapView(
|
|||
AppCompatResources.getDrawable(context, R.drawable.ic_baseline_location_on_24)
|
||||
}
|
||||
|
||||
fun MapView.onNodesChanged(nodes: Collection<NodeEntity>): List<MarkerWithLabel> {
|
||||
fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> {
|
||||
val nodesWithPosition = nodes.filter { it.validPosition != null }
|
||||
val ourNode = model.ourNodeInfo.value
|
||||
val gpsFormat = model.config.display.gpsFormat.number
|
||||
|
|
|
|||
|
|
@ -90,8 +90,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.database.entity.QuickChatAction
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
import com.geeksville.mesh.model.getChannel
|
||||
import com.geeksville.mesh.ui.components.NodeKeyStatusIcon
|
||||
|
|
@ -116,7 +116,7 @@ internal fun FragmentManager.navigateToMessages(contactKey: String, message: Str
|
|||
class MessagesFragment : Fragment(), Logging {
|
||||
private val model: UIViewModel by activityViewModels()
|
||||
|
||||
private fun navigateToMessages(node: NodeEntity) = node.user.let { user ->
|
||||
private fun navigateToMessages(node: Node) = node.user.let { user ->
|
||||
val hasPKC = model.ourNodeInfo.value?.hasPKC == true && node.hasPKC // TODO use meta.hasPKC
|
||||
val channel = if (hasPKC) DataPacket.PKC_CHANNEL_INDEX else node.channel
|
||||
val contactKey = "$channel${user.id}"
|
||||
|
|
@ -168,7 +168,7 @@ internal fun MessageScreen(
|
|||
contactKey: String,
|
||||
message: String,
|
||||
viewModel: UIViewModel = hiltViewModel(),
|
||||
navigateToMessages: (NodeEntity) -> Unit,
|
||||
navigateToMessages: (Node) -> Unit,
|
||||
navigateToNodeDetails: (Int) -> Unit,
|
||||
onNavigateBack: () -> Unit
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -60,16 +60,16 @@ import androidx.compose.ui.unit.sp
|
|||
import com.geeksville.mesh.DataPacket
|
||||
import com.geeksville.mesh.MessageStatus
|
||||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.ui.components.AutoLinkText
|
||||
import com.geeksville.mesh.ui.preview.NodeEntityPreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.preview.NodePreviewParameterProvider
|
||||
import com.geeksville.mesh.ui.theme.AppTheme
|
||||
|
||||
@Suppress("LongMethod", "CyclomaticComplexMethod")
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
internal fun MessageItem(
|
||||
node: NodeEntity,
|
||||
node: Node,
|
||||
messageText: String?,
|
||||
messageTime: String,
|
||||
messageStatus: MessageStatus?,
|
||||
|
|
@ -197,7 +197,7 @@ internal fun MessageItem(
|
|||
private fun MessageItemPreview() {
|
||||
AppTheme {
|
||||
MessageItem(
|
||||
node = NodeEntityPreviewParameterProvider().values.first(),
|
||||
node = NodePreviewParameterProvider().values.first(),
|
||||
messageText = stringResource(R.string.sample_message),
|
||||
messageTime = "10:00",
|
||||
messageStatus = MessageStatus.DELIVERED,
|
||||
|
|
|
|||
|
|
@ -20,19 +20,17 @@ package com.geeksville.mesh.ui.preview
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import com.geeksville.mesh.DeviceMetrics.Companion.currentTime
|
||||
import com.geeksville.mesh.MeshProtos
|
||||
import com.geeksville.mesh.database.entity.NodeEntity
|
||||
import com.geeksville.mesh.deviceMetrics
|
||||
import com.geeksville.mesh.environmentMetrics
|
||||
import com.geeksville.mesh.model.Node
|
||||
import com.geeksville.mesh.paxcount
|
||||
import com.geeksville.mesh.position
|
||||
import com.geeksville.mesh.telemetry
|
||||
import com.geeksville.mesh.user
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlin.random.Random
|
||||
|
||||
class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity> {
|
||||
|
||||
val mickeyMouse = NodeEntity(
|
||||
class NodePreviewParameterProvider : PreviewParameterProvider<Node> {
|
||||
val mickeyMouse = Node(
|
||||
num = 1955,
|
||||
user = user {
|
||||
id = "mickeyMouseId"
|
||||
|
|
@ -40,28 +38,22 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
|
|||
shortName = "MM"
|
||||
hwModel = MeshProtos.HardwareModel.TBEAM
|
||||
},
|
||||
longName = "Mickey Mouse",
|
||||
shortName = "MM",
|
||||
position = position {
|
||||
latitudeI = 338125110
|
||||
longitudeI = -1179189760
|
||||
altitude = 138
|
||||
satsInView = 4
|
||||
},
|
||||
latitude = 33.812511,
|
||||
longitude = -117.918976,
|
||||
lastHeard = currentTime(),
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceTelemetry = telemetry {
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
}
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
hopsAway = 0
|
||||
)
|
||||
|
|
@ -74,17 +66,13 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
|
|||
id = "minnieMouseId"
|
||||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
},
|
||||
longName = "Minnie Mouse",
|
||||
shortName = "MiMo",
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
position = position {},
|
||||
latitude = 0.0,
|
||||
longitude = 0.0,
|
||||
hopsAway = 1
|
||||
)
|
||||
|
||||
private val donaldDuck = NodeEntity(
|
||||
private val donaldDuck = Node(
|
||||
num = Random.nextInt(),
|
||||
position = position {
|
||||
latitudeI = 338052347
|
||||
|
|
@ -92,20 +80,16 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
|
|||
altitude = 121
|
||||
satsInView = 66
|
||||
},
|
||||
latitude = 33.8052347,
|
||||
longitude = -117.9208460,
|
||||
lastHeard = currentTime() - 300,
|
||||
channel = 0,
|
||||
snr = 12.5F,
|
||||
rssi = -42,
|
||||
deviceTelemetry = telemetry {
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
}
|
||||
deviceMetrics = deviceMetrics {
|
||||
channelUtilization = 2.4F
|
||||
airUtilTx = 3.5F
|
||||
batteryLevel = 85
|
||||
voltage = 3.7F
|
||||
uptimeSeconds = 3600
|
||||
},
|
||||
user = user {
|
||||
id = "donaldDuckId"
|
||||
|
|
@ -114,18 +98,14 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
|
|||
hwModel = MeshProtos.HardwareModel.HELTEC_V3
|
||||
publicKey = ByteString.copyFrom(ByteArray(32) { 1 })
|
||||
},
|
||||
longName = "Donald Duck, the Grand Duck of the Ducks",
|
||||
shortName = "DoDu",
|
||||
environmentTelemetry = telemetry {
|
||||
environmentMetrics = environmentMetrics {
|
||||
temperature = 28.0F
|
||||
relativeHumidity = 50.0F
|
||||
barometricPressure = 1013.25F
|
||||
gasResistance = 0.0F
|
||||
voltage = 3.7F
|
||||
current = 0.0F
|
||||
iaq = 100
|
||||
}
|
||||
environmentMetrics = environmentMetrics {
|
||||
temperature = 28.0F
|
||||
relativeHumidity = 50.0F
|
||||
barometricPressure = 1013.25F
|
||||
gasResistance = 0.0F
|
||||
voltage = 3.7F
|
||||
current = 0.0F
|
||||
iaq = 100
|
||||
},
|
||||
paxcounter = paxcount {
|
||||
wifi = 30
|
||||
|
|
@ -142,19 +122,15 @@ class NodeEntityPreviewParameterProvider : PreviewParameterProvider<NodeEntity>
|
|||
shortName = "myId"
|
||||
hwModel = MeshProtos.HardwareModel.UNSET
|
||||
},
|
||||
longName = "Meshtastic myId",
|
||||
shortName = null,
|
||||
environmentTelemetry = telemetry {
|
||||
environmentMetrics = environmentMetrics {}
|
||||
},
|
||||
environmentMetrics = environmentMetrics {},
|
||||
paxcounter = paxcount {},
|
||||
)
|
||||
|
||||
private val almostNothing = NodeEntity(
|
||||
private val almostNothing = Node(
|
||||
num = Random.nextInt(),
|
||||
)
|
||||
|
||||
override val values: Sequence<NodeEntity>
|
||||
override val values: Sequence<Node>
|
||||
get() = sequenceOf(
|
||||
mickeyMouse, // "this" node
|
||||
unknown,
|
||||
Loading…
Add table
Add a link
Reference in a new issue