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 0000000000..ace833c1d4 --- /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 36bf109355..396ff9f732 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 265a283151..51033d43c7 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 2bc8a1ee32..9753c3c5f0 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" /> -