mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Clean up list item component API (#3465)
This commit is contained in:
parent
1b9f0f9736
commit
51ccc59b24
18 changed files with 407 additions and 476 deletions
|
|
@ -35,8 +35,7 @@ import org.meshtastic.core.model.DeviceVersion
|
|||
import org.meshtastic.core.navigation.SettingsRoutes
|
||||
import org.meshtastic.core.service.ServiceAction
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.SettingsItemDetail
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusOrange
|
||||
|
|
@ -56,13 +55,13 @@ fun AdministrationSection(
|
|||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
TitledCard(stringResource(id = R.string.administration), modifier = modifier) {
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.request_metadata),
|
||||
leadingIcon = Icons.Default.Memory,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { onAction(NodeDetailAction.TriggerServiceAction(ServiceAction.GetDeviceMetadata(node.num))) },
|
||||
)
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.remote_admin),
|
||||
leadingIcon = Icons.Default.Settings,
|
||||
enabled = metricsState.isLocal || node.metadata != null,
|
||||
|
|
@ -81,10 +80,12 @@ fun AdministrationSection(
|
|||
else -> Icons.Default.ForkLeft
|
||||
}
|
||||
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.firmware_edition),
|
||||
icon = icon,
|
||||
leadingIcon = icon,
|
||||
supportingText = it.name,
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
firmwareVersion?.let { firmwareVersion ->
|
||||
|
|
@ -94,25 +95,31 @@ fun AdministrationSection(
|
|||
val deviceVersion = DeviceVersion(firmwareVersion.substringBeforeLast("."))
|
||||
val statusColor = deviceVersion.determineFirmwareStatusColor(latestStable, latestAlpha)
|
||||
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.installed_firmware_version),
|
||||
icon = Icons.Default.Memory,
|
||||
leadingIcon = Icons.Default.Memory,
|
||||
supportingText = firmwareVersion.substringBeforeLast("."),
|
||||
iconTint = statusColor,
|
||||
copyable = true,
|
||||
leadingIconTint = statusColor,
|
||||
trailingIcon = null,
|
||||
)
|
||||
HorizontalDivider()
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.latest_stable_firmware),
|
||||
icon = Icons.Default.Memory,
|
||||
leadingIcon = Icons.Default.Memory,
|
||||
supportingText = latestStable.id.substringBeforeLast(".").replace("v", ""),
|
||||
iconTint = MaterialTheme.colorScheme.StatusGreen,
|
||||
copyable = true,
|
||||
leadingIconTint = MaterialTheme.colorScheme.StatusGreen,
|
||||
trailingIcon = null,
|
||||
onClick = { onFirmwareSelect(latestStable) },
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.latest_alpha_firmware),
|
||||
icon = Icons.Default.Memory,
|
||||
leadingIcon = Icons.Default.Memory,
|
||||
supportingText = latestAlpha.id.substringBeforeLast(".").replace("v", ""),
|
||||
iconTint = MaterialTheme.colorScheme.StatusYellow,
|
||||
copyable = true,
|
||||
leadingIconTint = MaterialTheme.colorScheme.StatusYellow,
|
||||
trailingIcon = null,
|
||||
onClick = { onFirmwareSelect(latestAlpha) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,8 +35,8 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.SettingsItemSwitch
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.SwitchListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.feature.node.model.NodeDetailAction
|
||||
|
||||
|
|
@ -67,33 +67,33 @@ fun DeviceActions(
|
|||
onConfirmRemove = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.Remove(it))) },
|
||||
)
|
||||
TitledCard(title = stringResource(R.string.actions), modifier = modifier) {
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.share_contact),
|
||||
leadingIcon = Icons.Rounded.QrCode2,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { onAction(NodeDetailAction.ShareContact) },
|
||||
)
|
||||
if (!isLocal) {
|
||||
RemoteDeviceActions(node = node, lastTracerouteTime = lastTracerouteTime, onAction = onAction)
|
||||
}
|
||||
SettingsItemSwitch(
|
||||
SwitchListItem(
|
||||
text = stringResource(R.string.favorite),
|
||||
leadingIcon = if (node.isFavorite) Icons.Default.Star else Icons.Default.StarBorder,
|
||||
leadingIconTint = if (node.isFavorite) Color.Yellow else LocalContentColor.current,
|
||||
checked = node.isFavorite,
|
||||
onClick = { displayFavoriteDialog = true },
|
||||
)
|
||||
SettingsItemSwitch(
|
||||
SwitchListItem(
|
||||
text = stringResource(R.string.ignore),
|
||||
leadingIcon =
|
||||
if (node.isIgnored) Icons.AutoMirrored.Outlined.VolumeMute else Icons.AutoMirrored.Default.VolumeUp,
|
||||
checked = node.isIgnored,
|
||||
onClick = { displayIgnoreDialog = true },
|
||||
)
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.remove),
|
||||
leadingIcon = Icons.Rounded.Delete,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { displayRemoveDialog = true },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import coil3.compose.AsyncImage
|
|||
import coil3.request.ImageRequest
|
||||
import org.meshtastic.core.model.DeviceHardware
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItemDetail
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusGreen
|
||||
import org.meshtastic.core.ui.theme.StatusColors.StatusRed
|
||||
|
|
@ -71,26 +71,28 @@ fun DeviceDetailsSection(state: MetricsState, modifier: Modifier = Modifier) {
|
|||
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.hardware),
|
||||
icon = Icons.Default.Router,
|
||||
leadingIcon = Icons.Default.Router,
|
||||
supportingText = hwModelName,
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text =
|
||||
if (isSupported) {
|
||||
stringResource(R.string.supported)
|
||||
} else {
|
||||
stringResource(R.string.supported_by_community)
|
||||
},
|
||||
icon =
|
||||
leadingIcon =
|
||||
if (isSupported) {
|
||||
Icons.TwoTone.Verified
|
||||
} else {
|
||||
ImageVector.vectorResource(org.meshtastic.feature.node.R.drawable.unverified)
|
||||
},
|
||||
supportingText = null,
|
||||
iconTint = if (isSupported) colorScheme.StatusGreen else colorScheme.StatusRed,
|
||||
leadingIconTint = if (isSupported) colorScheme.StatusGreen else colorScheme.StatusRed,
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,119 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.combinedClickable
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextDecoration
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.model.util.GPSFormat
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import org.meshtastic.core.ui.theme.HyperlinkBlue
|
||||
import timber.log.Timber
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LinkedCoordinates(modifier: Modifier = Modifier, latitude: Double, longitude: Double, nodeName: String) {
|
||||
val context = LocalContext.current
|
||||
val clipboard: Clipboard = LocalClipboard.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val style =
|
||||
SpanStyle(
|
||||
color = HyperlinkBlue,
|
||||
fontStyle = MaterialTheme.typography.titleLarge.fontStyle,
|
||||
textDecoration = TextDecoration.Underline,
|
||||
)
|
||||
|
||||
val annotatedString = rememberAnnotatedString(latitude, longitude, nodeName, style)
|
||||
|
||||
Text(
|
||||
modifier =
|
||||
modifier.combinedClickable(
|
||||
onClick = { handleClick(context, annotatedString) },
|
||||
onLongClick = {
|
||||
coroutineScope.launch {
|
||||
clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("", annotatedString)))
|
||||
Timber.d("Copied to clipboard")
|
||||
}
|
||||
},
|
||||
),
|
||||
text = annotatedString,
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberAnnotatedString(latitude: Double, longitude: Double, nodeName: String, style: SpanStyle) =
|
||||
buildAnnotatedString {
|
||||
pushStringAnnotation(
|
||||
tag = "gps",
|
||||
annotation =
|
||||
"geo:0,0?q=$latitude,$longitude&z=17&label=${
|
||||
URLEncoder.encode(nodeName, "utf-8")
|
||||
}",
|
||||
)
|
||||
withStyle(style = style) {
|
||||
val gpsString = GPSFormat.toDec(latitude, longitude)
|
||||
append(gpsString)
|
||||
}
|
||||
pop()
|
||||
}
|
||||
|
||||
private fun handleClick(context: Context, annotatedString: AnnotatedString) {
|
||||
annotatedString.getStringAnnotations(tag = "gps", start = 0, end = annotatedString.length).firstOrNull()?.let {
|
||||
val uri = it.item.toUri()
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
|
||||
try {
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(context, "No application available to open this location!", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
Timber.d("Failed to open geo intent: $ex")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun LinkedCoordinatesPreview() {
|
||||
AppTheme { LinkedCoordinates(latitude = 37.7749, longitude = -122.4194, nodeName = "Test Node Name") }
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* Copyright (c) 2025 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/>.
|
||||
*/
|
||||
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ClipData
|
||||
import android.content.Intent
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.rounded.KeyboardArrowRight
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.Clipboard
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.core.net.toUri
|
||||
import kotlinx.coroutines.launch
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.util.GPSFormat
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.BasicListItem
|
||||
import org.meshtastic.core.ui.component.icon
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
import timber.log.Timber
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun LinkedCoordinatesItem(node: Node) {
|
||||
val context = LocalContext.current
|
||||
val clipboard: Clipboard = LocalClipboard.current
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
val ago = formatAgo(node.position.time)
|
||||
val coordinates = GPSFormat.toDec(node.latitude, node.longitude)
|
||||
|
||||
BasicListItem(
|
||||
text = stringResource(R.string.last_position_update),
|
||||
leadingIcon = Icons.Default.LocationOn,
|
||||
supportingText = "$ago • $coordinates",
|
||||
trailingContent = Icons.AutoMirrored.Rounded.KeyboardArrowRight.icon(),
|
||||
onClick = {
|
||||
val label = URLEncoder.encode(node.user.longName, "utf-8")
|
||||
val uri = "geo:0,0?q=${node.latitude},${node.longitude}&z=17&label=$label".toUri()
|
||||
val intent = Intent(Intent.ACTION_VIEW, uri).apply { addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) }
|
||||
|
||||
try {
|
||||
if (intent.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(intent)
|
||||
} else {
|
||||
Toast.makeText(context, "No application available to open this location!", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} catch (ex: ActivityNotFoundException) {
|
||||
Timber.d("Failed to open geo intent: $ex")
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
coroutineScope.launch { clipboard.setClipEntry(ClipEntry(ClipData.newPlainText("", coordinates))) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewLightDark
|
||||
@Composable
|
||||
private fun LinkedCoordinatesPreview() {
|
||||
AppTheme { LinkedCoordinatesItem(Node(0)) }
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.feature.node.model.LogsType
|
||||
import org.meshtastic.feature.node.model.MetricsState
|
||||
|
|
@ -57,7 +57,7 @@ fun MetricsSection(
|
|||
if (nonPositionLogs.isNotEmpty()) {
|
||||
TitledCard(title = stringResource(id = R.string.logs), modifier = modifier) {
|
||||
nonPositionLogs.forEach { type ->
|
||||
SettingsItem(text = stringResource(type.titleRes), leadingIcon = type.icon) {
|
||||
ListItem(text = stringResource(type.titleRes), leadingIcon = type.icon) {
|
||||
onAction(NodeDetailAction.Navigate(type.route))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import org.meshtastic.core.database.model.Node
|
|||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.model.util.formatUptime
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItemDetail
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
|
||||
|
||||
|
|
@ -80,48 +80,59 @@ fun NodeDetailsSection(node: Node, modifier: Modifier = Modifier) {
|
|||
|
||||
@Composable
|
||||
private fun MainNodeDetails(node: Node) {
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.long_name),
|
||||
icon = Icons.TwoTone.Person,
|
||||
leadingIcon = Icons.TwoTone.Person,
|
||||
supportingText = node.user.longName.ifEmpty { "???" },
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.short_name),
|
||||
icon = Icons.Outlined.Person,
|
||||
leadingIcon = Icons.Outlined.Person,
|
||||
supportingText = node.user.shortName.ifEmpty { "???" },
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.node_number),
|
||||
icon = Icons.Default.Numbers,
|
||||
leadingIcon = Icons.Default.Numbers,
|
||||
supportingText = node.num.toUInt().toString(),
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.user_id),
|
||||
icon = Icons.Default.Person,
|
||||
leadingIcon = Icons.Default.Person,
|
||||
supportingText = node.user.id,
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.role),
|
||||
icon = Icons.Default.Work,
|
||||
leadingIcon = Icons.Default.Work,
|
||||
supportingText = node.user.role.name,
|
||||
trailingIcon = null,
|
||||
)
|
||||
if (node.isEffectivelyUnmessageable) {
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.unmonitored_or_infrastructure),
|
||||
icon = Icons.Outlined.NoCell,
|
||||
supportingText = null,
|
||||
leadingIcon = Icons.Outlined.NoCell,
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
if (node.deviceMetrics.uptimeSeconds > 0) {
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.uptime),
|
||||
icon = Icons.Default.CheckCircle,
|
||||
leadingIcon = Icons.Default.CheckCircle,
|
||||
supportingText = formatUptime(node.deviceMetrics.uptimeSeconds),
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.node_sort_last_heard),
|
||||
icon = Icons.Default.History,
|
||||
leadingIcon = Icons.Default.History,
|
||||
supportingText = formatAgo(node.lastHeard),
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,26 +17,19 @@
|
|||
|
||||
package org.meshtastic.feature.node.component
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.LocationOn
|
||||
import androidx.compose.material.icons.filled.SocialDistance
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.model.util.formatAgo
|
||||
import org.meshtastic.core.model.util.toDistanceString
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.SettingsItemDetail
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.core.ui.component.TitledCard
|
||||
import org.meshtastic.feature.node.model.LogsType
|
||||
import org.meshtastic.feature.node.model.MetricsState
|
||||
|
|
@ -61,52 +54,39 @@ fun PositionSection(
|
|||
// Current position coordinates (linked)
|
||||
if (hasValidPosition) {
|
||||
InlineMap(node = node, Modifier.fillMaxWidth().height(200.dp))
|
||||
SettingsItemDetail(
|
||||
text = stringResource(R.string.last_position_update),
|
||||
icon = Icons.Default.LocationOn,
|
||||
supportingContent = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(formatAgo(node.position.time), style = MaterialTheme.typography.titleLarge)
|
||||
LinkedCoordinates(
|
||||
latitude = node.latitude,
|
||||
longitude = node.longitude,
|
||||
nodeName = node.user.longName,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
LinkedCoordinatesItem(node)
|
||||
}
|
||||
|
||||
// Distance (if available)
|
||||
if (distance != null && distance.isNotEmpty()) {
|
||||
SettingsItemDetail(
|
||||
ListItem(
|
||||
text = stringResource(R.string.node_sort_distance),
|
||||
icon = Icons.Default.SocialDistance,
|
||||
leadingIcon = Icons.Default.SocialDistance,
|
||||
supportingText = distance,
|
||||
copyable = true,
|
||||
trailingIcon = null,
|
||||
)
|
||||
}
|
||||
|
||||
// Exchange position action
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.exchange_position),
|
||||
leadingIcon = Icons.Default.LocationOn,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestPosition(node))) },
|
||||
)
|
||||
|
||||
// Node Map log
|
||||
if (availableLogs.contains(LogsType.NODE_MAP)) {
|
||||
SettingsItem(text = stringResource(LogsType.NODE_MAP.titleRes), leadingIcon = LogsType.NODE_MAP.icon) {
|
||||
ListItem(text = stringResource(LogsType.NODE_MAP.titleRes), leadingIcon = LogsType.NODE_MAP.icon) {
|
||||
onAction(NodeDetailAction.Navigate(LogsType.NODE_MAP.route))
|
||||
}
|
||||
}
|
||||
|
||||
// Positions Log
|
||||
if (availableLogs.contains(LogsType.POSITIONS)) {
|
||||
SettingsItem(text = stringResource(LogsType.POSITIONS.titleRes), leadingIcon = LogsType.POSITIONS.icon) {
|
||||
ListItem(text = stringResource(LogsType.POSITIONS.titleRes), leadingIcon = LogsType.POSITIONS.icon) {
|
||||
onAction(NodeDetailAction.Navigate(LogsType.POSITIONS.route))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,24 +24,24 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import org.meshtastic.core.database.model.Node
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.ListItem
|
||||
import org.meshtastic.feature.node.model.NodeDetailAction
|
||||
import org.meshtastic.feature.node.model.isEffectivelyUnmessageable
|
||||
|
||||
@Composable
|
||||
internal fun RemoteDeviceActions(node: Node, lastTracerouteTime: Long?, onAction: (NodeDetailAction) -> Unit) {
|
||||
if (!node.isEffectivelyUnmessageable) {
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.direct_message),
|
||||
leadingIcon = Icons.AutoMirrored.TwoTone.Message,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.DirectMessage(node))) },
|
||||
)
|
||||
}
|
||||
SettingsItem(
|
||||
ListItem(
|
||||
text = stringResource(id = R.string.exchange_userinfo),
|
||||
leadingIcon = Icons.Default.Person,
|
||||
trailingContent = {},
|
||||
trailingIcon = null,
|
||||
onClick = { onAction(NodeDetailAction.HandleNodeMenuAction(NodeMenuAction.RequestUserInfo(node))) },
|
||||
)
|
||||
TracerouteButton(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.meshtastic.core.strings.R
|
||||
import org.meshtastic.core.ui.component.SettingsItem
|
||||
import org.meshtastic.core.ui.component.BasicListItem
|
||||
import org.meshtastic.core.ui.theme.AppTheme
|
||||
|
||||
private const val COOL_DOWN_TIME_MS = 30000L
|
||||
|
|
@ -70,7 +70,7 @@ private fun TracerouteButton(text: String, progress: Float, onClick: () -> Unit)
|
|||
|
||||
val stroke = Stroke(width = with(LocalDensity.current) { 2.dp.toPx() }, cap = StrokeCap.Round)
|
||||
|
||||
SettingsItem(
|
||||
BasicListItem(
|
||||
text = text,
|
||||
enabled = !isCoolingDown,
|
||||
leadingIcon = Icons.Default.Route,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue