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" /> -