From 9ecae6c0e105022f035dcaa1ed1dc24c19a6dfc2 Mon Sep 17 00:00:00 2001 From: Davis Date: Tue, 27 Feb 2024 14:43:47 -0700 Subject: [PATCH] Node position to compose (#877) * Move battery info to compose - always show voltage level and icons to match battery percentage Use tool text in preview, rather than actually set text value Simplify node info layout to avoid defining margins on everything * Move node position to Compose * Update hyperlink color to match previous value * Use compose preview in layout editor * Use compose preview in layout editor * Add simple preview for use in layout --- .../geeksville/mesh/ui/LinkedCoordinates.kt | 80 +++++++++++++++++++ .../com/geeksville/mesh/ui/UsersFragment.kt | 36 ++++----- .../com/geeksville/mesh/ui/theme/Color.kt | 4 +- .../main/res/layout/adapter_node_layout.xml | 12 ++- 4 files changed, 104 insertions(+), 28 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt diff --git a/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt b/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt new file mode 100644 index 000000000..ace833c1d --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/ui/LinkedCoordinates.kt @@ -0,0 +1,80 @@ +package com.geeksville.mesh.ui + +import androidx.compose.foundation.text.ClickableText +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource +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.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.geeksville.mesh.Position +import com.geeksville.mesh.R +import com.geeksville.mesh.android.BuildUtils.debug +import com.geeksville.mesh.ui.theme.AppTheme +import com.geeksville.mesh.ui.theme.HyperlinkBlue +import java.net.URLEncoder + +@Composable +fun LinkedCoordinates( + position: Position?, + format: Int, + nodeName: String? +) { + if (position != null) { + val uriHandler = LocalUriHandler.current + val style = SpanStyle( + color = HyperlinkBlue, + fontSize = MaterialTheme.typography.button.fontSize, + textDecoration = TextDecoration.Underline + ) + val name = nodeName ?: stringResource(id = R.string.unknown_username) + val annotatedString = buildAnnotatedString { + pushStringAnnotation( + tag = "gps", + annotation = "geo:${position.latitude},${position.longitude}?z=17&label=${ + URLEncoder.encode(name, "utf-8") + }" + ) + withStyle(style = style) { + append(position.gpsString(format)) + } + pop() + } + ClickableText( + text = annotatedString, + maxLines = 1, + onClick = { offset -> + debug("Clicked on link") + annotatedString.getStringAnnotations(tag = "gps", start = offset, end = offset) + .firstOrNull()?.let { + uriHandler.openUri(it.item) + } + } + ) + } +} + +@Composable +@Preview(showBackground = true) +@Preview(showBackground = true, uiMode = android.content.res.Configuration.UI_MODE_NIGHT_YES) +fun LinkedCoordinatesPreview( + @PreviewParameter(GPSFormatPreviewParameterProvider::class) format: Int +) { + AppTheme { + LinkedCoordinates( + position = Position(37.7749, -122.4194, 0), + format = format, + nodeName = "Test Node Name" + ) + } +} + +class GPSFormatPreviewParameterProvider: PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf(0, 1, 2) +} \ No newline at end of file diff --git a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt index 36bf10935..396ff9f73 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/UsersFragment.kt @@ -5,7 +5,6 @@ import android.content.res.ColorStateList import android.graphics.Color import android.os.Bundle import android.text.SpannableString -import android.text.method.LinkMovementMethod import android.text.style.StrikethroughSpan import android.view.LayoutInflater import android.view.MenuItem @@ -14,8 +13,6 @@ import android.view.ViewGroup import android.view.animation.LinearInterpolator import androidx.appcompat.widget.PopupMenu import androidx.core.animation.doOnEnd -import androidx.core.content.ContextCompat -import androidx.core.text.HtmlCompat import androidx.fragment.app.activityViewModels import androidx.lifecycle.asLiveData import androidx.lifecycle.lifecycleScope @@ -23,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.RecyclerView import com.geeksville.mesh.NodeInfo +import com.geeksville.mesh.Position import com.geeksville.mesh.R import com.geeksville.mesh.android.Logging import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding @@ -35,7 +33,6 @@ import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.net.URLEncoder @AndroidEntryPoint class UsersFragment : ScreenFragment("Users"), Logging { @@ -58,11 +55,11 @@ class UsersFragment : ScreenFragment("Users"), Logging { val chipNode = itemView.chipNode val nodeNameView = itemView.nodeNameView val distanceView = itemView.distanceView - val coordsView = itemView.coordsView val lastTime = itemView.lastConnectionView val signalView = itemView.signalView val envMetrics = itemView.envMetrics val background = itemView.nodeCard + val nodePosition = itemView.nodePosition val batteryInfo = itemView.batteryInfo fun blink() { @@ -86,12 +83,23 @@ class UsersFragment : ScreenFragment("Users"), Logging { } } - fun bind(batteryLevel: Int?, voltage: Float?) { + fun bind( + batteryLevel: Int?, + voltage: Float?, + position: Position?, + gpsFormat: Int, + nodeName: String? + ) { batteryInfo.setContent { AppTheme { BatteryInfo(batteryLevel, voltage) } } + nodePosition.setContent { + AppTheme { + LinkedCoordinates(position, gpsFormat, nodeName) + } + } } } @@ -240,29 +248,17 @@ class UsersFragment : ScreenFragment("Users"), Logging { val user = n.user val (textColor, nodeColor) = n.colors val isIgnored: Boolean = ignoreIncomingList.contains(n.num) + val name = user?.longName - holder.bind(n.batteryLevel, n.voltage) + holder.bind(n.batteryLevel, n.voltage, n.validPosition, gpsFormat, name) with(holder.chipNode) { text = (user?.shortName ?: "UNK").strikeIf(isIgnored) chipBackgroundColor = ColorStateList.valueOf(nodeColor) setTextColor(textColor) } - val name = user?.longName ?: getString(R.string.unknown_username) holder.nodeNameView.text = name - val pos = n.validPosition - if (pos != null) { - val html = "${pos.gpsString(gpsFormat)}" - holder.coordsView.text = HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY) - holder.coordsView.movementMethod = LinkMovementMethod.getInstance() - holder.coordsView.visibility = View.VISIBLE - } else { - holder.coordsView.visibility = View.INVISIBLE - } - val ourNodeInfo = nodes[0] val distance = ourNodeInfo.distanceStr(n, displayUnits) if (distance != null) { diff --git a/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt b/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt index 265a28315..51033d43c 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/theme/Color.kt @@ -17,4 +17,6 @@ val LightRed = Color(0xFFFFB3B3) val MeshtasticGreen = Color(0xFF67EA94) val AlmostWhite = Color(0xB3FFFFFF) -val AlmostBlack = Color(0x8A000000) \ No newline at end of file +val AlmostBlack = Color(0x8A000000) + +val HyperlinkBlue = Color(0xFF43C3B0) \ No newline at end of file diff --git a/app/src/main/res/layout/adapter_node_layout.xml b/app/src/main/res/layout/adapter_node_layout.xml index 2bc8a1ee3..9753c3c5f 100644 --- a/app/src/main/res/layout/adapter_node_layout.xml +++ b/app/src/main/res/layout/adapter_node_layout.xml @@ -53,17 +53,15 @@ tools:text="@string/sample_distance" /> -