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
This commit is contained in:
Davis 2024-02-27 14:43:47 -07:00 committed by GitHub
parent 7b49f57af6
commit 9ecae6c0e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 104 additions and 28 deletions

View file

@ -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<Int> {
override val values: Sequence<Int>
get() = sequenceOf(0, 1, 2)
}

View file

@ -5,7 +5,6 @@ import android.content.res.ColorStateList
import android.graphics.Color import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.text.SpannableString import android.text.SpannableString
import android.text.method.LinkMovementMethod
import android.text.style.StrikethroughSpan import android.text.style.StrikethroughSpan
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
@ -14,8 +13,6 @@ import android.view.ViewGroup
import android.view.animation.LinearInterpolator import android.view.animation.LinearInterpolator
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.animation.doOnEnd import androidx.core.animation.doOnEnd
import androidx.core.content.ContextCompat
import androidx.core.text.HtmlCompat
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -23,6 +20,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.geeksville.mesh.NodeInfo import com.geeksville.mesh.NodeInfo
import com.geeksville.mesh.Position
import com.geeksville.mesh.R import com.geeksville.mesh.R
import com.geeksville.mesh.android.Logging import com.geeksville.mesh.android.Logging
import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding import com.geeksville.mesh.databinding.AdapterNodeLayoutBinding
@ -35,7 +33,6 @@ import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import java.net.URLEncoder
@AndroidEntryPoint @AndroidEntryPoint
class UsersFragment : ScreenFragment("Users"), Logging { class UsersFragment : ScreenFragment("Users"), Logging {
@ -58,11 +55,11 @@ class UsersFragment : ScreenFragment("Users"), Logging {
val chipNode = itemView.chipNode val chipNode = itemView.chipNode
val nodeNameView = itemView.nodeNameView val nodeNameView = itemView.nodeNameView
val distanceView = itemView.distanceView val distanceView = itemView.distanceView
val coordsView = itemView.coordsView
val lastTime = itemView.lastConnectionView val lastTime = itemView.lastConnectionView
val signalView = itemView.signalView val signalView = itemView.signalView
val envMetrics = itemView.envMetrics val envMetrics = itemView.envMetrics
val background = itemView.nodeCard val background = itemView.nodeCard
val nodePosition = itemView.nodePosition
val batteryInfo = itemView.batteryInfo val batteryInfo = itemView.batteryInfo
fun blink() { 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 { batteryInfo.setContent {
AppTheme { AppTheme {
BatteryInfo(batteryLevel, voltage) BatteryInfo(batteryLevel, voltage)
} }
} }
nodePosition.setContent {
AppTheme {
LinkedCoordinates(position, gpsFormat, nodeName)
}
}
} }
} }
@ -240,29 +248,17 @@ class UsersFragment : ScreenFragment("Users"), Logging {
val user = n.user val user = n.user
val (textColor, nodeColor) = n.colors val (textColor, nodeColor) = n.colors
val isIgnored: Boolean = ignoreIncomingList.contains(n.num) 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) { with(holder.chipNode) {
text = (user?.shortName ?: "UNK").strikeIf(isIgnored) text = (user?.shortName ?: "UNK").strikeIf(isIgnored)
chipBackgroundColor = ColorStateList.valueOf(nodeColor) chipBackgroundColor = ColorStateList.valueOf(nodeColor)
setTextColor(textColor) setTextColor(textColor)
} }
val name = user?.longName ?: getString(R.string.unknown_username)
holder.nodeNameView.text = name holder.nodeNameView.text = name
val pos = n.validPosition
if (pos != null) {
val html = "<a href='geo:${pos.latitude},${pos.longitude}?z=17&label=${
URLEncoder.encode(name, "utf-8")
}'>${pos.gpsString(gpsFormat)}</a>"
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 ourNodeInfo = nodes[0]
val distance = ourNodeInfo.distanceStr(n, displayUnits) val distance = ourNodeInfo.distanceStr(n, displayUnits)
if (distance != null) { if (distance != null) {

View file

@ -17,4 +17,6 @@ val LightRed = Color(0xFFFFB3B3)
val MeshtasticGreen = Color(0xFF67EA94) val MeshtasticGreen = Color(0xFF67EA94)
val AlmostWhite = Color(0xB3FFFFFF) val AlmostWhite = Color(0xB3FFFFFF)
val AlmostBlack = Color(0x8A000000) val AlmostBlack = Color(0x8A000000)
val HyperlinkBlue = Color(0xFF43C3B0)

View file

@ -53,17 +53,15 @@
tools:text="@string/sample_distance" tools:text="@string/sample_distance"
/> />
<TextView <androidx.compose.ui.platform.ComposeView
android:id="@+id/coords_view" android:id="@+id/nodePosition"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toEndOf="@+id/chip_node" app:layout_constraintStart_toEndOf="@+id/chip_node"
app:layout_constraintTop_toBottomOf="@+id/nodeNameView" app:layout_constraintTop_toBottomOf="@+id/nodeNameView"
app:layout_constraintVertical_bias="0.0" android:layout_marginStart="8dp"
tools:text="@string/sample_coords" android:layout_marginTop="8dp"
tools:composableName="com.geeksville.mesh.ui.LinkedCoordinatesKt.LinkedCoordinatesPreview"
/> />
<androidx.compose.ui.platform.ComposeView <androidx.compose.ui.platform.ComposeView