Skip to content

Commit

Permalink
refactor: migrate DebugFragment RecyclerView to Compose
Browse files Browse the repository at this point in the history
  • Loading branch information
andrekir committed Jul 2, 2024
1 parent c7a3488 commit a543bcb
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 222 deletions.
85 changes: 0 additions & 85 deletions app/src/main/java/com/geeksville/mesh/ui/DebugAdapter.kt

This file was deleted.

215 changes: 171 additions & 44 deletions app/src/main/java/com/geeksville/mesh/ui/DebugFragment.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,51 @@
package com.geeksville.mesh.ui

import android.content.res.Configuration
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.Icon
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.asLiveData
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.geeksville.mesh.CoroutineDispatchers
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.R
import com.geeksville.mesh.database.entity.MeshLog
import com.geeksville.mesh.databinding.FragmentDebugBinding
import com.geeksville.mesh.model.DebugViewModel
import com.geeksville.mesh.ui.theme.AppTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import java.text.DateFormat
import java.util.Locale
import javax.inject.Inject

@AndroidEntryPoint
class DebugFragment : Fragment() {
Expand All @@ -30,9 +57,6 @@ class DebugFragment : Fragment() {

private val model: DebugViewModel by viewModels()

@Inject
lateinit var dispatchers: CoroutineDispatchers

override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
Expand All @@ -43,11 +67,6 @@ class DebugFragment : Fragment() {

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.debug_recyclerview)
val adapter = DebugAdapter(requireContext())

recyclerView.adapter = adapter
recyclerView.layoutManager = LinearLayoutManager(requireContext())

binding.clearButton.setOnClickListener {
model.deleteAllLogs()
Expand All @@ -56,12 +75,25 @@ class DebugFragment : Fragment() {
binding.closeButton.setOnClickListener {
parentFragmentManager.popBackStack()
}
model.meshLog
.map(this::annotateMeshLogs)
.flowOn(dispatchers.default)
.asLiveData()
.observe(viewLifecycleOwner) { logs ->
logs?.let { adapter.setLogs(it) }

binding.debugListView.setContent {
val listState = rememberLazyListState()
val logs by model.meshLog.collectAsStateWithLifecycle()

LaunchedEffect(logs) {
if (listState.firstVisibleItemIndex < 3 && !listState.isScrollInProgress) {
listState.scrollToItem(0)
}
}

AppTheme {
LazyColumn(
modifier = Modifier.fillMaxSize(),
state = listState,
) {
items(logs, key = { it.uuid }) { log -> DebugItem(annotateMeshLog(log)) }
}
}
}
}

Expand All @@ -71,33 +103,34 @@ class DebugFragment : Fragment() {
}

/**
* Transform the input list by enhancing the raw message with annotations.
* Transform the input [MeshLog] by enhancing the raw message with annotations.
*/
private fun annotateMeshLogs(logs: List<MeshLog>): List<MeshLog> {
return logs.map { meshLog ->
val annotated = when (meshLog.message_type) {
"Packet" -> {
meshLog.meshPacket?.let { packet ->
annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
}
}
"NodeInfo" -> {
meshLog.nodeInfo?.let { nodeInfo ->
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
}
private fun annotateMeshLog(meshLog: MeshLog): MeshLog {
val annotated = when (meshLog.message_type) {
"Packet" -> {
meshLog.meshPacket?.let { packet ->
annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
}
"MyNodeInfo" -> {
meshLog.myNodeInfo?.let { nodeInfo ->
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
}
}

"NodeInfo" -> {
meshLog.nodeInfo?.let { nodeInfo ->
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
}
else -> null
}
if (annotated == null) {
meshLog
} else {
meshLog.copy(raw_message = annotated)

"MyNodeInfo" -> {
meshLog.myNodeInfo?.let { nodeInfo ->
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
}
}

else -> null
}
return if (annotated == null) {
meshLog
} else {
meshLog.copy(raw_message = annotated)
}
}

Expand Down Expand Up @@ -133,4 +166,98 @@ class DebugFragment : Fragment() {
private fun Int.asNodeId(): String {
return "!%08x".format(Locale.getDefault(), this)
}
}
}

private val REGEX_ANNOTATED_NODE_ID = Regex("\\(![0-9a-fA-F]{8}\\)$", RegexOption.MULTILINE)

@Composable
internal fun DebugItem(log: MeshLog) {
val timeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)

Card(
modifier = Modifier
.fillMaxWidth()
.padding(4.dp),
elevation = 4.dp,
shape = RoundedCornerShape(12.dp),
) {
Surface {
Column(
modifier = Modifier.padding(8.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 8.dp),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = log.message_type,
modifier = Modifier.weight(1f),
style = TextStyle(fontWeight = FontWeight.Bold),
)
Icon(
painterResource(R.drawable.cloud_download_outline_24),
contentDescription = null,
tint = Color.Gray.copy(alpha = 0.6f),
modifier = Modifier.padding(end = 8.dp),
)
Text(
text = timeFormat.format(log.received_date),
style = TextStyle(fontWeight = FontWeight.Bold),
)
}

val style = SpanStyle(
color = colorResource(id = R.color.colorAnnotation),
fontStyle = FontStyle.Italic,
)
val annotatedString = buildAnnotatedString {
append(log.raw_message)
REGEX_ANNOTATED_NODE_ID.findAll(log.raw_message).toList().reversed().forEach {
addStyle(style = style, start = it.range.first, end = it.range.last + 1)
}
}

Text(
text = annotatedString,
softWrap = false,
style = TextStyle(
fontSize = 9.sp,
fontFamily = FontFamily.Monospace,
)
)
}
}
}
}

@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun DebugScreenPreview() {
AppTheme {
DebugItem(
MeshLog(
uuid = "",
message_type = "NodeInfo",
received_date = 1601251258000L,
raw_message = "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",
)
)
}
}
Loading

0 comments on commit a543bcb

Please sign in to comment.