mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
Fix Debug panel: Decoded payload not showing (#2555)
This commit is contained in:
parent
ceabafb545
commit
c1408816a4
1 changed files with 183 additions and 333 deletions
|
|
@ -99,9 +99,7 @@ private val REGEX_ANNOTATED_NODE_ID = Regex("\\(![0-9a-fA-F]{8}\\)$", RegexOptio
|
|||
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun DebugScreen(
|
||||
viewModel: DebugViewModel = hiltViewModel(),
|
||||
) {
|
||||
internal fun DebugScreen(viewModel: DebugViewModel = hiltViewModel()) {
|
||||
val listState = rememberLazyListState()
|
||||
val logs by viewModel.meshLog.collectAsStateWithLifecycle()
|
||||
val searchState by viewModel.searchState.collectAsStateWithLifecycle()
|
||||
|
|
@ -111,13 +109,12 @@ 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) {
|
||||
viewModel.filterManager.filterLogs(logs, filterTexts, filterMode).toImmutableList()
|
||||
}
|
||||
val filteredLogs =
|
||||
remember(logs, filterTexts, filterMode) {
|
||||
viewModel.filterManager.filterLogs(logs, filterTexts, filterMode).toImmutableList()
|
||||
}
|
||||
|
||||
LaunchedEffect(filteredLogs) {
|
||||
viewModel.updateFilteredLogs(filteredLogs)
|
||||
}
|
||||
LaunchedEffect(filteredLogs) { viewModel.updateFilteredLogs(filteredLogs) }
|
||||
|
||||
val shouldAutoScroll by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } }
|
||||
if (shouldAutoScroll) {
|
||||
|
|
@ -133,28 +130,19 @@ internal fun DebugScreen(
|
|||
listState.requestScrollToItem(searchState.allMatches[searchState.currentMatchIndex].logIndex)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize(), state = listState) {
|
||||
stickyHeader {
|
||||
val animatedAlpha by animateFloatAsState(
|
||||
targetValue = if (!listState.isScrollInProgress) 1.0f else 0f,
|
||||
label = "alpha"
|
||||
)
|
||||
val animatedAlpha by
|
||||
animateFloatAsState(targetValue = if (!listState.isScrollInProgress) 1.0f else 0f, label = "alpha")
|
||||
DebugSearchStateviewModelDefaults(
|
||||
modifier = Modifier.graphicsLayer(
|
||||
alpha = animatedAlpha
|
||||
),
|
||||
modifier = Modifier.graphicsLayer(alpha = animatedAlpha),
|
||||
searchState = searchState,
|
||||
filterTexts = filterTexts,
|
||||
presetFilters = viewModel.presetFilters,
|
||||
logs = logs,
|
||||
filterMode = filterMode,
|
||||
onFilterModeChange = { filterMode = it }
|
||||
onFilterModeChange = { filterMode = it },
|
||||
)
|
||||
}
|
||||
items(filteredLogs, key = { it.uuid }) { log ->
|
||||
|
|
@ -163,9 +151,7 @@ internal fun DebugScreen(
|
|||
log = log,
|
||||
searchText = searchState.searchText,
|
||||
isSelected = selectedLogId == log.uuid,
|
||||
onLogClick = {
|
||||
viewModel.setSelectedLogId(if (selectedLogId == log.uuid) null else log.uuid)
|
||||
}
|
||||
onLogClick = { viewModel.setSelectedLogId(if (selectedLogId == log.uuid) null else log.uuid) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -178,49 +164,43 @@ internal fun DebugItem(
|
|||
modifier: Modifier = Modifier,
|
||||
searchText: String = "",
|
||||
isSelected: Boolean = false,
|
||||
onLogClick: () -> Unit = {}
|
||||
onLogClick: () -> Unit = {},
|
||||
) {
|
||||
val colorScheme = MaterialTheme.colorScheme
|
||||
|
||||
Card(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(4.dp),
|
||||
colors = CardDefaults.cardColors(
|
||||
containerColor = if (isSelected) {
|
||||
modifier = modifier.fillMaxWidth().padding(4.dp),
|
||||
colors =
|
||||
CardDefaults.cardColors(
|
||||
containerColor =
|
||||
if (isSelected) {
|
||||
colorScheme.primary.copy(alpha = 0.1f)
|
||||
} else {
|
||||
colorScheme.surface
|
||||
}
|
||||
},
|
||||
),
|
||||
border = if (isSelected) {
|
||||
border =
|
||||
if (isSelected) {
|
||||
BorderStroke(2.dp, colorScheme.primary)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
) {
|
||||
SelectionContainer {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(if (isSelected) 12.dp else 8.dp)
|
||||
.fillMaxWidth()
|
||||
.clickable { onLogClick() }
|
||||
modifier = Modifier.padding(if (isSelected) 12.dp else 8.dp).fillMaxWidth().clickable { onLogClick() },
|
||||
) {
|
||||
DebugItemHeader(
|
||||
log = log,
|
||||
searchText = searchText,
|
||||
isSelected = isSelected,
|
||||
theme = colorScheme
|
||||
)
|
||||
DebugItemHeader(log = log, searchText = searchText, isSelected = isSelected, theme = colorScheme)
|
||||
val messageAnnotatedString = rememberAnnotatedLogMessage(log, searchText)
|
||||
Text(
|
||||
text = messageAnnotatedString,
|
||||
softWrap = false,
|
||||
style = TextStyle(
|
||||
style =
|
||||
TextStyle(
|
||||
fontSize = if (isSelected) 12.sp else 9.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
color = colorScheme.onSurface,
|
||||
),
|
||||
)
|
||||
// Show decoded payload if available, with search highlighting
|
||||
if (!log.decodedPayload.isNullOrBlank()) {
|
||||
|
|
@ -229,7 +209,7 @@ internal fun DebugItem(
|
|||
isSelected = isSelected,
|
||||
colorScheme = colorScheme,
|
||||
searchText = searchText,
|
||||
modifier = Modifier.weight(1f)
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -238,79 +218,60 @@ internal fun DebugItem(
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun DebugItemHeader(
|
||||
log: UiMeshLog,
|
||||
searchText: String,
|
||||
isSelected: Boolean,
|
||||
theme: ColorScheme
|
||||
) {
|
||||
private fun DebugItemHeader(log: UiMeshLog, searchText: String, isSelected: Boolean, theme: ColorScheme) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(bottom = if (isSelected) 12.dp else 8.dp),
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = if (isSelected) 12.dp else 8.dp),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
val typeAnnotatedString = rememberAnnotatedString(
|
||||
text = log.messageType,
|
||||
searchText = searchText
|
||||
)
|
||||
val typeAnnotatedString = rememberAnnotatedString(text = log.messageType, searchText = searchText)
|
||||
Text(
|
||||
text = typeAnnotatedString,
|
||||
modifier = Modifier.weight(1f),
|
||||
style = TextStyle(
|
||||
style =
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = if (isSelected) 16.sp else 14.sp,
|
||||
color = theme.onSurface
|
||||
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}")
|
||||
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 = fullLogText,
|
||||
modifier = Modifier.padding(start = 8.dp)
|
||||
)
|
||||
CopyIconButton(valueToCopy = fullLogText, modifier = Modifier.padding(start = 8.dp))
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FileDownload,
|
||||
contentDescription = stringResource(id = R.string.logs),
|
||||
tint = Color.Gray.copy(alpha = 0.6f),
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
val dateAnnotatedString = rememberAnnotatedString(
|
||||
text = log.formattedReceivedDate,
|
||||
searchText = searchText
|
||||
)
|
||||
val dateAnnotatedString = rememberAnnotatedString(text = log.formattedReceivedDate, searchText = searchText)
|
||||
Text(
|
||||
text = dateAnnotatedString,
|
||||
style = TextStyle(
|
||||
style =
|
||||
TextStyle(
|
||||
fontWeight = FontWeight.Bold,
|
||||
fontSize = if (isSelected) 14.sp else 12.sp,
|
||||
color = theme.onSurface
|
||||
color = theme.onSurface,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun rememberAnnotatedString(
|
||||
text: String,
|
||||
searchText: String
|
||||
): AnnotatedString {
|
||||
private fun rememberAnnotatedString(text: String, searchText: String): AnnotatedString {
|
||||
val theme = MaterialTheme.colorScheme
|
||||
val highlightStyle = SpanStyle(
|
||||
background = theme.primary.copy(alpha = 0.3f),
|
||||
color = theme.onSurface
|
||||
)
|
||||
val highlightStyle = SpanStyle(background = theme.primary.copy(alpha = 0.3f), color = theme.onSurface)
|
||||
|
||||
return remember(text, searchText) {
|
||||
buildAnnotatedString {
|
||||
|
|
@ -318,11 +279,7 @@ private fun rememberAnnotatedString(
|
|||
if (searchText.isNotEmpty()) {
|
||||
searchText.split(" ").forEach { term ->
|
||||
Regex(Regex.escape(term), RegexOption.IGNORE_CASE).findAll(text).forEach { match ->
|
||||
addStyle(
|
||||
style = highlightStyle,
|
||||
start = match.range.first,
|
||||
end = match.range.last + 1
|
||||
)
|
||||
addStyle(style = highlightStyle, start = match.range.first, end = match.range.last + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -333,38 +290,23 @@ private fun rememberAnnotatedString(
|
|||
@Composable
|
||||
private fun rememberAnnotatedLogMessage(log: UiMeshLog, searchText: String): AnnotatedString {
|
||||
val theme = MaterialTheme.colorScheme
|
||||
val style = SpanStyle(
|
||||
color = colorResource(id = R.color.colorAnnotation),
|
||||
fontStyle = FontStyle.Italic,
|
||||
)
|
||||
val highlightStyle = SpanStyle(
|
||||
background = theme.primary.copy(alpha = 0.3f),
|
||||
color = theme.onSurface
|
||||
)
|
||||
val style = SpanStyle(color = colorResource(id = R.color.colorAnnotation), fontStyle = FontStyle.Italic)
|
||||
val highlightStyle = SpanStyle(background = theme.primary.copy(alpha = 0.3f), color = theme.onSurface)
|
||||
|
||||
return remember(log.uuid, searchText) {
|
||||
buildAnnotatedString {
|
||||
append(log.logMessage)
|
||||
|
||||
// Add node ID annotations
|
||||
REGEX_ANNOTATED_NODE_ID.findAll(log.logMessage).toList().reversed()
|
||||
.forEach {
|
||||
addStyle(
|
||||
style = style,
|
||||
start = it.range.first,
|
||||
end = it.range.last + 1
|
||||
)
|
||||
}
|
||||
REGEX_ANNOTATED_NODE_ID.findAll(log.logMessage).toList().reversed().forEach {
|
||||
addStyle(style = style, start = it.range.first, end = it.range.last + 1)
|
||||
}
|
||||
|
||||
// Add search highlight annotations
|
||||
if (searchText.isNotEmpty()) {
|
||||
searchText.split(" ").forEach { term ->
|
||||
Regex(Regex.escape(term), RegexOption.IGNORE_CASE).findAll(log.logMessage).forEach { match ->
|
||||
addStyle(
|
||||
style = highlightStyle,
|
||||
start = match.range.first,
|
||||
end = match.range.last + 1
|
||||
)
|
||||
addStyle(style = highlightStyle, start = match.range.first, end = match.range.last + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -381,22 +323,23 @@ private fun DebugPacketPreview() {
|
|||
uuid = "",
|
||||
messageType = "NodeInfo",
|
||||
formattedReceivedDate = "9/27/20, 8:00:58 PM",
|
||||
logMessage = "from: 2885173132\n" +
|
||||
"decoded {\n" +
|
||||
" position {\n" +
|
||||
" altitude: 60\n" +
|
||||
" battery_level: 81\n" +
|
||||
" latitude_i: 411111136\n" +
|
||||
" longitude_i: -711111805\n" +
|
||||
" time: 1600390966\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"hop_limit: 3\n" +
|
||||
"id: 1737414295\n" +
|
||||
"rx_snr: 9.5\n" +
|
||||
"rx_time: 316400569\n" +
|
||||
"to: -1409790708",
|
||||
)
|
||||
logMessage =
|
||||
"from: 2885173132\n" +
|
||||
"decoded {\n" +
|
||||
" position {\n" +
|
||||
" altitude: 60\n" +
|
||||
" battery_level: 81\n" +
|
||||
" latitude_i: 411111136\n" +
|
||||
" longitude_i: -711111805\n" +
|
||||
" time: 1600390966\n" +
|
||||
" }\n" +
|
||||
"}\n" +
|
||||
"hop_limit: 3\n" +
|
||||
"id: 1737414295\n" +
|
||||
"rx_snr: 9.5\n" +
|
||||
"rx_time: 316400569\n" +
|
||||
"to: -1409790708",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -410,9 +353,9 @@ private fun DebugItemWithSearchHighlightPreview() {
|
|||
uuid = "1",
|
||||
messageType = "TextMessage",
|
||||
formattedReceivedDate = "9/27/20, 8:00:58 PM",
|
||||
logMessage = "Hello world! This is a test message with some keywords to search for."
|
||||
logMessage = "Hello world! This is a test message with some keywords to search for.",
|
||||
),
|
||||
searchText = "test message"
|
||||
searchText = "test message",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -426,8 +369,8 @@ private fun DebugItemPositionPreview() {
|
|||
uuid = "2",
|
||||
messageType = "Position",
|
||||
formattedReceivedDate = "9/27/20, 8:01:15 PM",
|
||||
logMessage = "Position update from node (!a1b2c3d4) at coordinates 40.7128, -74.0060"
|
||||
)
|
||||
logMessage = "Position update from node (!a1b2c3d4) at coordinates 40.7128, -74.0060",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -441,10 +384,11 @@ private fun DebugItemErrorPreview() {
|
|||
uuid = "3",
|
||||
messageType = "Error",
|
||||
formattedReceivedDate = "9/27/20, 8:02:30 PM",
|
||||
logMessage = "Connection failed: timeout after 30 seconds\n" +
|
||||
"Retry attempt: 3/5\n" +
|
||||
"Last known position: 40.7128, -74.0060"
|
||||
)
|
||||
logMessage =
|
||||
"Connection failed: timeout after 30 seconds\n" +
|
||||
"Retry attempt: 3/5\n" +
|
||||
"Last known position: 40.7128, -74.0060",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -458,16 +402,17 @@ private fun DebugItemLongMessagePreview() {
|
|||
uuid = "4",
|
||||
messageType = "Waypoint",
|
||||
formattedReceivedDate = "9/27/20, 8:03:45 PM",
|
||||
logMessage = "Waypoint created:\n" +
|
||||
" Name: Home Base\n" +
|
||||
" Description: Primary meeting location\n" +
|
||||
" Latitude: 40.7128\n" +
|
||||
" Longitude: -74.0060\n" +
|
||||
" Altitude: 100m\n" +
|
||||
" Icon: 🏠\n" +
|
||||
" Created by: (!a1b2c3d4)\n" +
|
||||
" Expires: 2025-12-31 23:59:59"
|
||||
)
|
||||
logMessage =
|
||||
"Waypoint created:\n" +
|
||||
" Name: Home Base\n" +
|
||||
" Description: Primary meeting location\n" +
|
||||
" Latitude: 40.7128\n" +
|
||||
" Longitude: -74.0060\n" +
|
||||
" Altitude: 100m\n" +
|
||||
" Icon: 🏠\n" +
|
||||
" Created by: (!a1b2c3d4)\n" +
|
||||
" Expires: 2025-12-31 23:59:59",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -481,9 +426,9 @@ private fun DebugItemSelectedPreview() {
|
|||
uuid = "5",
|
||||
messageType = "TextMessage",
|
||||
formattedReceivedDate = "9/27/20, 8:04:20 PM",
|
||||
logMessage = "This is a selected log item with larger font sizes for better readability."
|
||||
logMessage = "This is a selected log item with larger font sizes for better readability.",
|
||||
),
|
||||
isSelected = true
|
||||
isSelected = true,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -492,26 +437,15 @@ private fun DebugItemSelectedPreview() {
|
|||
@Composable
|
||||
private fun DebugMenuActionsPreview() {
|
||||
AppTheme {
|
||||
Row(
|
||||
modifier = Modifier.padding(16.dp)
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { /* Preview only */ },
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
Row(modifier = Modifier.padding(16.dp)) {
|
||||
IconButton(onClick = { /* Preview only */ }, modifier = Modifier.padding(4.dp)) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FileDownload,
|
||||
contentDescription = stringResource(id = R.string.debug_logs_export)
|
||||
contentDescription = stringResource(id = R.string.debug_logs_export),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { /* Preview only */ },
|
||||
modifier = Modifier.padding(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(id = R.string.debug_clear)
|
||||
)
|
||||
IconButton(onClick = { /* Preview only */ }, modifier = Modifier.padding(4.dp)) {
|
||||
Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(id = R.string.debug_clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -523,50 +457,33 @@ private fun DebugMenuActionsPreview() {
|
|||
private fun DebugScreenEmptyPreview() {
|
||||
AppTheme {
|
||||
Surface {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
stickyHeader {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Surface(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
OutlinedTextField(
|
||||
value = "",
|
||||
onValueChange = { },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(end = 8.dp),
|
||||
onValueChange = {},
|
||||
modifier = Modifier.weight(1f).padding(end = 8.dp),
|
||||
placeholder = { Text("Search in logs...") },
|
||||
singleLine = true
|
||||
singleLine = true,
|
||||
)
|
||||
TextButton(
|
||||
onClick = { }
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Text(
|
||||
text = "Filters",
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
TextButton(onClick = {}) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(text = "Filters", style = TextStyle(fontWeight = FontWeight.Bold))
|
||||
Icon(
|
||||
imageVector = Icons.TwoTone.FilterAltOff,
|
||||
contentDescription = stringResource(id = R.string.debug_filters)
|
||||
contentDescription = stringResource(id = R.string.debug_filters),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -577,29 +494,16 @@ private fun DebugScreenEmptyPreview() {
|
|||
}
|
||||
// Empty state
|
||||
item {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(32.dp),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
Box(modifier = Modifier.fillMaxWidth().padding(32.dp), contentAlignment = Alignment.Center) {
|
||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(
|
||||
text = "No Debug Logs",
|
||||
style = TextStyle(
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
style = TextStyle(fontSize = 18.sp, fontWeight = FontWeight.Bold),
|
||||
)
|
||||
Text(
|
||||
text = "Debug logs will appear here when available",
|
||||
style = TextStyle(
|
||||
fontSize = 14.sp,
|
||||
color = Color.Gray
|
||||
),
|
||||
modifier = Modifier.padding(top = 8.dp)
|
||||
style = TextStyle(fontSize = 14.sp, color = Color.Gray),
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -614,12 +518,14 @@ private fun DebugScreenEmptyPreview() {
|
|||
@Suppress("detekt:LongMethod") // big preview
|
||||
private fun DebugScreenWithSampleDataPreview() {
|
||||
AppTheme {
|
||||
val sampleLogs = listOf(
|
||||
UiMeshLog(
|
||||
uuid = "1",
|
||||
messageType = "NodeInfo",
|
||||
formattedReceivedDate = "9/27/20, 8:00:58 PM",
|
||||
logMessage = "from: 2885173132\n" +
|
||||
val sampleLogs =
|
||||
listOf(
|
||||
UiMeshLog(
|
||||
uuid = "1",
|
||||
messageType = "NodeInfo",
|
||||
formattedReceivedDate = "9/27/20, 8:00:58 PM",
|
||||
logMessage =
|
||||
"from: 2885173132\n" +
|
||||
"decoded {\n" +
|
||||
" position {\n" +
|
||||
" altitude: 60\n" +
|
||||
|
|
@ -633,100 +539,74 @@ private fun DebugScreenWithSampleDataPreview() {
|
|||
"id: 1737414295\n" +
|
||||
"rx_snr: 9.5\n" +
|
||||
"rx_time: 316400569\n" +
|
||||
"to: -1409790708"
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "2",
|
||||
messageType = "TextMessage",
|
||||
formattedReceivedDate = "9/27/20, 8:01:15 PM",
|
||||
logMessage = "Hello from node (!a1b2c3d4)! How's the weather today?"
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "3",
|
||||
messageType = "Position",
|
||||
formattedReceivedDate = "9/27/20, 8:02:30 PM",
|
||||
logMessage = "Position update: 40.7128, -74.0060, altitude: 100m, battery: 85%"
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "4",
|
||||
messageType = "Waypoint",
|
||||
formattedReceivedDate = "9/27/20, 8:03:45 PM",
|
||||
logMessage = "New waypoint created: 'Meeting Point' at 40.7589, -73.9851"
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "5",
|
||||
messageType = "Error",
|
||||
formattedReceivedDate = "9/27/20, 8:04:20 PM",
|
||||
logMessage = "Connection timeout - retrying in 5 seconds..."
|
||||
"to: -1409790708",
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "2",
|
||||
messageType = "TextMessage",
|
||||
formattedReceivedDate = "9/27/20, 8:01:15 PM",
|
||||
logMessage = "Hello from node (!a1b2c3d4)! How's the weather today?",
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "3",
|
||||
messageType = "Position",
|
||||
formattedReceivedDate = "9/27/20, 8:02:30 PM",
|
||||
logMessage = "Position update: 40.7128, -74.0060, altitude: 100m, battery: 85%",
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "4",
|
||||
messageType = "Waypoint",
|
||||
formattedReceivedDate = "9/27/20, 8:03:45 PM",
|
||||
logMessage = "New waypoint created: 'Meeting Point' at 40.7589, -73.9851",
|
||||
),
|
||||
UiMeshLog(
|
||||
uuid = "5",
|
||||
messageType = "Error",
|
||||
formattedReceivedDate = "9/27/20, 8:04:20 PM",
|
||||
logMessage = "Connection timeout - retrying in 5 seconds...",
|
||||
),
|
||||
)
|
||||
)
|
||||
|
||||
// Note: This preview shows the UI structure but won't have actual data
|
||||
// since the ViewModel isn't injected in previews
|
||||
Surface {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
LazyColumn(modifier = Modifier.fillMaxSize()) {
|
||||
stickyHeader {
|
||||
Surface(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
Surface(modifier = Modifier.fillMaxWidth().padding(8.dp)) {
|
||||
Column(modifier = Modifier.padding(8.dp)) {
|
||||
Text(
|
||||
text = "Debug Screen Preview",
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
)
|
||||
Text(
|
||||
text = "Search and filter controls would appear here",
|
||||
style = TextStyle(fontSize = 12.sp, color = Color.Gray)
|
||||
style = TextStyle(fontSize = 12.sp, color = Color.Gray),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
items(sampleLogs) { log ->
|
||||
DebugItem(log = log)
|
||||
}
|
||||
items(sampleLogs) { log -> DebugItem(log = log) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DebugMenuActions(
|
||||
viewModel: DebugViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
fun DebugMenuActions(viewModel: DebugViewModel = hiltViewModel(), modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val logs by viewModel.meshLog.collectAsStateWithLifecycle()
|
||||
var showDeleteLogsDialog by remember { mutableStateOf(false) }
|
||||
|
||||
IconButton(
|
||||
onClick = {
|
||||
scope.launch {
|
||||
exportAllLogs(context, logs)
|
||||
}
|
||||
},
|
||||
modifier = modifier.padding(4.dp)
|
||||
) {
|
||||
IconButton(onClick = { scope.launch { exportAllLogs(context, logs) } }, modifier = modifier.padding(4.dp)) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.FileDownload,
|
||||
contentDescription = stringResource(id = R.string.debug_logs_export)
|
||||
contentDescription = stringResource(id = R.string.debug_logs_export),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = { showDeleteLogsDialog = true },
|
||||
modifier = modifier.padding(4.dp)
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = stringResource(id = R.string.debug_clear)
|
||||
)
|
||||
IconButton(onClick = { showDeleteLogsDialog = true }, modifier = modifier.padding(4.dp)) {
|
||||
Icon(imageVector = Icons.Default.Delete, contentDescription = stringResource(id = R.string.debug_clear))
|
||||
}
|
||||
if (showDeleteLogsDialog) {
|
||||
SimpleAlertDialog(
|
||||
|
|
@ -736,7 +616,7 @@ fun DebugMenuActions(
|
|||
showDeleteLogsDialog = false
|
||||
viewModel.deleteAllLogs()
|
||||
},
|
||||
onDismiss = { showDeleteLogsDialog = false }
|
||||
onDismiss = { showDeleteLogsDialog = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -767,28 +647,16 @@ private suspend fun exportAllLogs(context: Context, logs: List<UiMeshLog>) = wit
|
|||
|
||||
// Notify user of success
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Logs exported to ${logFile.absolutePath}",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Toast.makeText(context, "Logs exported to ${logFile.absolutePath}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
} catch (e: SecurityException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Permission denied: Cannot write to Downloads folder",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Toast.makeText(context, "Permission denied: Cannot write to Downloads folder", Toast.LENGTH_LONG).show()
|
||||
warn("Error:SecurityException: " + e.toString())
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
withContext(Dispatchers.Main) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
"Failed to write log file: ${e.message}",
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
Toast.makeText(context, "Failed to write log file: ${e.message}", Toast.LENGTH_LONG).show()
|
||||
}
|
||||
warn("Error:IOException: " + e.toString())
|
||||
}
|
||||
|
|
@ -800,42 +668,31 @@ private fun DecodedPayloadBlock(
|
|||
isSelected: Boolean,
|
||||
colorScheme: ColorScheme,
|
||||
searchText: String = "",
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
val commonTextStyle = TextStyle(
|
||||
fontSize = if (isSelected) 10.sp else 8.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = colorScheme.primary
|
||||
)
|
||||
val commonTextStyle =
|
||||
TextStyle(fontSize = if (isSelected) 10.sp else 8.sp, fontWeight = FontWeight.Bold, color = colorScheme.primary)
|
||||
|
||||
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)
|
||||
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(
|
||||
style =
|
||||
TextStyle(
|
||||
fontSize = if (isSelected) 10.sp else 8.sp,
|
||||
fontFamily = FontFamily.Monospace,
|
||||
color = colorScheme.onSurface.copy(alpha = 0.8f)
|
||||
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)
|
||||
modifier = Modifier.padding(start = 16.dp, bottom = 0.dp),
|
||||
)
|
||||
Text(text = "}", style = commonTextStyle, modifier = Modifier.padding(start = 8.dp, bottom = 4.dp))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -843,23 +700,16 @@ private fun DecodedPayloadBlock(
|
|||
private fun rememberAnnotatedDecodedPayload(
|
||||
decodedPayload: String,
|
||||
searchText: String,
|
||||
colorScheme: ColorScheme
|
||||
colorScheme: ColorScheme,
|
||||
): AnnotatedString {
|
||||
val highlightStyle = SpanStyle(
|
||||
background = colorScheme.primary.copy(alpha = 0.3f),
|
||||
color = colorScheme.onSurface
|
||||
)
|
||||
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
|
||||
)
|
||||
addStyle(style = highlightStyle, start = match.range.first, end = match.range.last + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue