feat/2482 Make decoded payload accessible to filters/search/copies (#2483)

This commit is contained in:
DaneEvans 2025-07-22 00:26:25 +10:00 committed by GitHub
parent d208314758
commit c665b5528c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 114 additions and 47 deletions

View file

@ -48,6 +48,7 @@ import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.AdminProtos
import com.geeksville.mesh.PaxcountProtos
import com.geeksville.mesh.StoreAndForwardProtos
import com.geeksville.mesh.ui.debug.FilterMode
data class SearchMatch(
val logIndex: Int,
@ -140,7 +141,11 @@ class LogSearchManager {
.map { match -> SearchMatch(logIndex, match.range.first, match.range.last, "type") }
val dateMatches = regex.findAll(log.formattedReceivedDate)
.map { match -> SearchMatch(logIndex, match.range.first, match.range.last, "date") }
messageMatches + typeMatches + dateMatches
val decodedPayloadMatches = log.decodedPayload?.let { decoded ->
regex.findAll(decoded)
.map { match -> SearchMatch(logIndex, match.range.first, match.range.last, "decodedPayload") }
} ?: emptySequence()
messageMatches + typeMatches + dateMatches + decodedPayloadMatches
}
}.sortedBy { it.start }
}
@ -160,6 +165,30 @@ class LogFilterManager {
fun updateFilteredLogs(logs: List<DebugViewModel.UiMeshLog>) {
_filteredLogs.value = logs
}
fun filterLogs(
logs: List<DebugViewModel.UiMeshLog>,
filterTexts: List<String>,
filterMode: FilterMode
): List<DebugViewModel.UiMeshLog> {
if (filterTexts.isEmpty()) return logs
return logs.filter { log ->
when (filterMode) {
FilterMode.OR -> filterTexts.any { filterText ->
log.logMessage.contains(filterText, ignoreCase = true) ||
log.messageType.contains(filterText, ignoreCase = true) ||
log.formattedReceivedDate.contains(filterText, ignoreCase = true) ||
(log.decodedPayload?.contains(filterText, ignoreCase = true) == true)
}
FilterMode.AND -> filterTexts.all { filterText ->
log.logMessage.contains(filterText, ignoreCase = true) ||
log.messageType.contains(filterText, ignoreCase = true) ||
log.formattedReceivedDate.contains(filterText, ignoreCase = true) ||
(log.decodedPayload?.contains(filterText, ignoreCase = true) == true)
}
}
}
}
}
private const val HEX_FORMAT = "%02x"

View file

@ -110,24 +110,9 @@ internal fun DebugScreen(
var filterMode by remember { mutableStateOf(FilterMode.OR) }
// Use the new filterLogs method to include decodedPayload in filtering
val filteredLogs = remember(logs, filterTexts, filterMode) {
logs.filter { log ->
if (filterTexts.isEmpty()) {
true
} else { when (filterMode) {
FilterMode.OR -> filterTexts.any { filterText ->
log.logMessage.contains(filterText, ignoreCase = true) ||
log.messageType.contains(filterText, ignoreCase = true) ||
log.formattedReceivedDate.contains(filterText, ignoreCase = true)
}
FilterMode.AND -> filterTexts.all { filterText ->
log.logMessage.contains(filterText, ignoreCase = true) ||
log.messageType.contains(filterText, ignoreCase = true) ||
log.formattedReceivedDate.contains(filterText, ignoreCase = true)
}
}
}
}.toImmutableList()
viewModel.filterManager.filterLogs(logs, filterTexts, filterMode).toImmutableList()
}
LaunchedEffect(filteredLogs) {
@ -237,12 +222,14 @@ internal fun DebugItem(
color = colorScheme.onSurface
)
)
// Show decoded payload if available
// Show decoded payload if available, with search highlighting
if (!log.decodedPayload.isNullOrBlank()) {
DecodedPayloadBlock(
decodedPayload = log.decodedPayload,
isSelected = isSelected,
colorScheme = colorScheme
colorScheme = colorScheme,
searchText = searchText,
modifier = Modifier.weight(1f)
)
}
}
@ -277,8 +264,20 @@ private fun DebugItemHeader(
color = theme.onSurface
),
)
// Copy full log: message + decoded payload if present
val fullLogText = remember(log.logMessage, log.decodedPayload) {
buildString {
append(log.logMessage)
if (!log.decodedPayload.isNullOrBlank()) {
append("\n\nDecoded Payload:\n{")
append("\n")
append(log.decodedPayload)
append("\n}")
}
}
}
CopyIconButton(
valueToCopy = log.logMessage,
valueToCopy = fullLogText,
modifier = Modifier.padding(start = 8.dp)
)
Icon(
@ -756,6 +755,12 @@ private suspend fun exportAllLogs(context: Context, logs: List<UiMeshLog>) = wit
logs.forEach { log ->
writer.write("${log.formattedReceivedDate} [${log.messageType}]\n")
writer.write(log.logMessage)
if (!log.decodedPayload.isNullOrBlank()) {
writer.write("\n\nDecoded Payload:\n{")
writer.write("\n")
writer.write(log.decodedPayload)
writer.write("\n}")
}
writer.write("\n\n")
}
}
@ -793,7 +798,9 @@ private suspend fun exportAllLogs(context: Context, logs: List<UiMeshLog>) = wit
private fun DecodedPayloadBlock(
decodedPayload: String,
isSelected: Boolean,
colorScheme: ColorScheme
colorScheme: ColorScheme,
searchText: String = "",
modifier: Modifier = Modifier
) {
val commonTextStyle = TextStyle(
@ -802,29 +809,60 @@ private fun DecodedPayloadBlock(
color = colorScheme.primary
)
Text(
text = stringResource(id = R.string.debug_decoded_payload),
style = commonTextStyle,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
)
Text(
text = "{",
style = commonTextStyle,
modifier = Modifier.padding(start = 8.dp, bottom = 2.dp)
)
Text(
text = decodedPayload,
softWrap = true,
style = TextStyle(
fontSize = if (isSelected) 10.sp else 8.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
),
modifier = Modifier.padding(start = 16.dp, bottom = 0.dp)
)
Text(
text = "}",
style = commonTextStyle,
modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)
)
Column(modifier = modifier) {
Text(
text = stringResource(id = R.string.debug_decoded_payload),
style = commonTextStyle,
modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)
)
Text(
text = "{",
style = commonTextStyle,
modifier = Modifier.padding(start = 8.dp, bottom = 2.dp)
)
val annotatedPayload = rememberAnnotatedDecodedPayload(decodedPayload, searchText, colorScheme)
Text(
text = annotatedPayload,
softWrap = true,
style = TextStyle(
fontSize = if (isSelected) 10.sp else 8.sp,
fontFamily = FontFamily.Monospace,
color = colorScheme.onSurface.copy(alpha = 0.8f)
),
modifier = Modifier.padding(start = 16.dp, bottom = 0.dp)
)
Text(
text = "}",
style = commonTextStyle,
modifier = Modifier.padding(start = 8.dp, bottom = 4.dp)
)
}
}
@Composable
private fun rememberAnnotatedDecodedPayload(
decodedPayload: String,
searchText: String,
colorScheme: ColorScheme
): AnnotatedString {
val highlightStyle = SpanStyle(
background = colorScheme.primary.copy(alpha = 0.3f),
color = colorScheme.onSurface
)
return remember(decodedPayload, searchText) {
buildAnnotatedString {
append(decodedPayload)
if (searchText.isNotEmpty()) {
searchText.split(" ").forEach { term ->
Regex(Regex.escape(term), RegexOption.IGNORE_CASE).findAll(decodedPayload).forEach { match ->
addStyle(
style = highlightStyle,
start = match.range.first,
end = match.range.last + 1
)
}
}
}
}
}
}