kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
refactor: migrate `DebugPanel` to Compose
rodzic
91c8c7809a
commit
e33cf85df6
|
@ -1,31 +1,36 @@
|
|||
package com.geeksville.mesh.database
|
||||
|
||||
import com.geeksville.mesh.CoroutineDispatchers
|
||||
import com.geeksville.mesh.Portnums
|
||||
import com.geeksville.mesh.MeshProtos.MeshPacket
|
||||
import com.geeksville.mesh.TelemetryProtos.Telemetry
|
||||
import com.geeksville.mesh.database.dao.MeshLogDao
|
||||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.conflate
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.Lazy<MeshLogDao>) {
|
||||
class MeshLogRepository @Inject constructor(
|
||||
private val meshLogDaoLazy: dagger.Lazy<MeshLogDao>,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) {
|
||||
private val meshLogDao by lazy {
|
||||
meshLogDaoLazy.get()
|
||||
}
|
||||
|
||||
suspend fun getAllLogs(maxItems: Int = MAX_ITEMS): Flow<List<MeshLog>> = withContext(Dispatchers.IO) {
|
||||
meshLogDao.getAllLogs(maxItems)
|
||||
}
|
||||
fun getAllLogs(maxItems: Int = MAX_ITEMS): Flow<List<MeshLog>> = meshLogDao.getAllLogs(maxItems)
|
||||
.flowOn(dispatchers.io)
|
||||
.conflate()
|
||||
|
||||
suspend fun getAllLogsInReceiveOrder(maxItems: Int = MAX_ITEMS): Flow<List<MeshLog>> = withContext(Dispatchers.IO) {
|
||||
fun getAllLogsInReceiveOrder(maxItems: Int = MAX_ITEMS): Flow<List<MeshLog>> =
|
||||
meshLogDao.getAllLogsInReceiveOrder(maxItems)
|
||||
}
|
||||
.flowOn(dispatchers.io)
|
||||
.conflate()
|
||||
|
||||
private fun parseTelemetryLog(log: MeshLog): Telemetry? = runCatching {
|
||||
Telemetry.parseFrom(log.fromRadio.packet.decoded.payload)
|
||||
|
@ -37,7 +42,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
|
|||
meshLogDao.getLogsFrom(nodeNum, Portnums.PortNum.TELEMETRY_APP_VALUE, MAX_MESH_PACKETS)
|
||||
.distinctUntilChanged()
|
||||
.mapLatest { list -> list.mapNotNull(::parseTelemetryLog) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.flowOn(dispatchers.io)
|
||||
|
||||
fun getLogsFrom(
|
||||
nodeNum: Int,
|
||||
|
@ -45,7 +50,7 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
|
|||
maxItem: Int = MAX_MESH_PACKETS,
|
||||
): Flow<List<MeshLog>> = meshLogDao.getLogsFrom(nodeNum, portNum, maxItem)
|
||||
.distinctUntilChanged()
|
||||
.flowOn(Dispatchers.IO)
|
||||
.flowOn(dispatchers.io)
|
||||
|
||||
/*
|
||||
* Retrieves MeshPackets matching 'nodeNum' and 'portNum'.
|
||||
|
@ -57,21 +62,21 @@ class MeshLogRepository @Inject constructor(private val meshLogDaoLazy: dagger.L
|
|||
portNum: Int = Portnums.PortNum.UNKNOWN_APP_VALUE,
|
||||
): Flow<List<MeshPacket>> = getLogsFrom(nodeNum, portNum)
|
||||
.mapLatest { list -> list.map { it.fromRadio.packet } }
|
||||
.flowOn(Dispatchers.IO)
|
||||
.flowOn(dispatchers.io)
|
||||
|
||||
suspend fun insert(log: MeshLog) = withContext(Dispatchers.IO) {
|
||||
suspend fun insert(log: MeshLog) = withContext(dispatchers.io) {
|
||||
meshLogDao.insert(log)
|
||||
}
|
||||
|
||||
suspend fun deleteAll() = withContext(Dispatchers.IO) {
|
||||
suspend fun deleteAll() = withContext(dispatchers.io) {
|
||||
meshLogDao.deleteAll()
|
||||
}
|
||||
|
||||
suspend fun deleteLog(uuid: String) = withContext(Dispatchers.IO) {
|
||||
suspend fun deleteLog(uuid: String) = withContext(dispatchers.io) {
|
||||
meshLogDao.deleteLog(uuid)
|
||||
}
|
||||
|
||||
suspend fun deleteLogs(nodeNum: Int, portNum: Int) = withContext(Dispatchers.IO) {
|
||||
suspend fun deleteLogs(nodeNum: Int, portNum: Int) = withContext(dispatchers.io) {
|
||||
meshLogDao.deleteLogs(nodeNum, portNum)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,8 +7,9 @@ import com.geeksville.mesh.database.MeshLogRepository
|
|||
import com.geeksville.mesh.database.entity.MeshLog
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -16,15 +17,10 @@ import javax.inject.Inject
|
|||
class DebugViewModel @Inject constructor(
|
||||
private val meshLogRepository: MeshLogRepository,
|
||||
) : ViewModel(), Logging {
|
||||
|
||||
private val _meshLog = MutableStateFlow<List<MeshLog>>(emptyList())
|
||||
val meshLog: StateFlow<List<MeshLog>> = _meshLog
|
||||
val meshLog: StateFlow<List<MeshLog>> = meshLogRepository.getAllLogs()
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5_000), emptyList())
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
meshLogRepository.getAllLogs().collect { _meshLog.value = it }
|
||||
}
|
||||
|
||||
debug("DebugViewModel created")
|
||||
}
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ 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.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
@ -15,10 +16,17 @@ import androidx.compose.foundation.lazy.items
|
|||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.outlined.CloudDownload
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
|
@ -26,8 +34,10 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
|
@ -37,12 +47,12 @@ import androidx.compose.ui.text.font.FontWeight
|
|||
import androidx.compose.ui.tooling.preview.PreviewLightDark
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
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
|
||||
|
@ -51,133 +61,138 @@ import java.util.Locale
|
|||
|
||||
@AndroidEntryPoint
|
||||
class DebugFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentDebugBinding? = null
|
||||
|
||||
// This property is only valid between onCreateView and onDestroyView.
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private val model: DebugViewModel by viewModels()
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentDebugBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
return ComposeView(requireContext()).apply {
|
||||
setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
|
||||
setBackgroundColor(ContextCompat.getColor(context, R.color.colorAdvancedBackground))
|
||||
setContent {
|
||||
val viewModel: DebugViewModel = hiltViewModel()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.clearButton.setOnClickListener {
|
||||
model.deleteAllLogs()
|
||||
}
|
||||
|
||||
binding.closeButton.setOnClickListener {
|
||||
parentFragmentManager.popBackStack()
|
||||
}
|
||||
|
||||
binding.debugListView.setContent {
|
||||
val listState = rememberLazyListState()
|
||||
val logs by model.meshLog.collectAsStateWithLifecycle()
|
||||
|
||||
val shouldAutoScroll by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } }
|
||||
if (shouldAutoScroll) {
|
||||
LaunchedEffect(logs) {
|
||||
if (!listState.isScrollInProgress) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppTheme {
|
||||
SelectionContainer {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
) {
|
||||
items(logs, key = { it.uuid }) { log -> DebugItem(annotateMeshLog(log)) }
|
||||
AppTheme {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = { Text(stringResource(id = R.string.debug_panel)) },
|
||||
navigationIcon = {
|
||||
IconButton(onClick = { parentFragmentManager.popBackStack() }) {
|
||||
Icon(
|
||||
Icons.AutoMirrored.Filled.ArrowBack,
|
||||
stringResource(id = R.string.navigate_back),
|
||||
)
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
Button(onClick = viewModel::deleteAllLogs) {
|
||||
Text(text = stringResource(R.string.clear))
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
) { innerPadding ->
|
||||
DebugScreen(
|
||||
viewModel = viewModel,
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform the input [MeshLog] by enhancing the raw message with annotations.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
"NodeInfo" -> {
|
||||
meshLog.nodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
|
||||
}
|
||||
}
|
||||
|
||||
"MyNodeInfo" -> {
|
||||
meshLog.myNodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
|
||||
}
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
return if (annotated == null) {
|
||||
meshLog
|
||||
} else {
|
||||
meshLog.copy(raw_message = annotated)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate the raw message string with the node IDs provided, in hex, if they are present.
|
||||
*/
|
||||
private fun annotateRawMessage(rawMessage: String, vararg nodeIds: Int): String {
|
||||
val msg = StringBuilder(rawMessage)
|
||||
var mutated = false
|
||||
nodeIds.forEach { nodeId ->
|
||||
mutated = mutated or msg.annotateNodeId(nodeId)
|
||||
}
|
||||
return if (mutated) {
|
||||
return msg.toString()
|
||||
} else {
|
||||
rawMessage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a single node ID integer in the string and annotate it with the hex equivalent
|
||||
* if found.
|
||||
*/
|
||||
private fun StringBuilder.annotateNodeId(nodeId: Int): Boolean {
|
||||
val nodeIdStr = nodeId.toUInt().toString()
|
||||
indexOf(nodeIdStr).takeIf { it >= 0 }?.let { idx ->
|
||||
insert(idx + nodeIdStr.length, " (${nodeId.asNodeId()})")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
/**
|
||||
* Transform the input [MeshLog] by enhancing the raw message with annotations.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
"NodeInfo" -> meshLog.nodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.num)
|
||||
}
|
||||
|
||||
"MyNodeInfo" -> meshLog.myNodeInfo?.let { nodeInfo ->
|
||||
annotateRawMessage(meshLog.raw_message, nodeInfo.myNodeNum)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}
|
||||
return if (annotated == null) {
|
||||
meshLog
|
||||
} else {
|
||||
meshLog.copy(raw_message = annotated)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Annotate the raw message string with the node IDs provided, in hex, if they are present.
|
||||
*/
|
||||
private fun annotateRawMessage(rawMessage: String, vararg nodeIds: Int): String {
|
||||
val msg = StringBuilder(rawMessage)
|
||||
var mutated = false
|
||||
nodeIds.forEach { nodeId ->
|
||||
mutated = mutated or msg.annotateNodeId(nodeId)
|
||||
}
|
||||
return if (mutated) {
|
||||
return msg.toString()
|
||||
} else {
|
||||
rawMessage
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Look for a single node ID integer in the string and annotate it with the hex equivalent
|
||||
* if found.
|
||||
*/
|
||||
private fun StringBuilder.annotateNodeId(nodeId: Int): Boolean {
|
||||
val nodeIdStr = nodeId.toUInt().toString()
|
||||
indexOf(nodeIdStr).takeIf { it >= 0 }?.let { idx ->
|
||||
insert(idx + nodeIdStr.length, " (${nodeId.asNodeId()})")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun Int.asNodeId(): String {
|
||||
return "!%08x".format(Locale.getDefault(), this)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DebugScreen(
|
||||
viewModel: DebugViewModel = hiltViewModel(),
|
||||
contentPadding: PaddingValues,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val logs by viewModel.meshLog.collectAsStateWithLifecycle()
|
||||
|
||||
val shouldAutoScroll by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } }
|
||||
if (shouldAutoScroll) {
|
||||
LaunchedEffect(logs) {
|
||||
if (!listState.isScrollInProgress) {
|
||||
listState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SelectionContainer {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState,
|
||||
contentPadding = contentPadding,
|
||||
) {
|
||||
items(logs, key = { it.uuid }) { log -> DebugItem(annotateMeshLog(log)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun DebugItem(log: MeshLog) {
|
||||
val timeFormat = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
||||
|
@ -205,8 +220,8 @@ internal fun DebugItem(log: MeshLog) {
|
|||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
)
|
||||
Icon(
|
||||
painterResource(R.drawable.cloud_download_outline_24),
|
||||
contentDescription = null,
|
||||
imageVector = Icons.Outlined.CloudDownload,
|
||||
contentDescription = stringResource(id = R.string.logs),
|
||||
tint = Color.Gray.copy(alpha = 0.6f),
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M8,13H10.55V10H13.45V13H16L12,17L8,13M19.35,10.04C21.95,10.22 24,12.36 24,15A5,5 0 0,1 19,20H6A6,6 0 0,1 0,14C0,10.91 2.34,8.36 5.35,8.04C6.6,5.64 9.11,4 12,4C15.64,4 18.67,6.59 19.35,10.04M19,18A3,3 0 0,0 22,15C22,13.45 20.78,12.14 19.22,12.04L17.69,11.93L17.39,10.43C16.88,7.86 14.62,6 12,6C9.94,6 8.08,7.14 7.13,8.97L6.63,9.92L5.56,10.03C3.53,10.24 2,11.95 2,14A4,4 0 0,0 6,18H19Z" />
|
||||
</vector>
|
|
@ -1,58 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorAdvancedBackground">
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/debugListView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/clearButton" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/clearButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:text="@string/clear"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/closeButton"
|
||||
style="@style/Widget.MaterialComponents.Button.Icon"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:insetLeft="0dp"
|
||||
android:insetTop="0dp"
|
||||
android:insetRight="0dp"
|
||||
android:insetBottom="0dp"
|
||||
app:backgroundTint="#D1D1D1"
|
||||
app:icon="@android:drawable/ic_menu_close_clear_cancel"
|
||||
app:iconPadding="0dp"
|
||||
app:iconSize="24dp"
|
||||
app:iconTint="#000000"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/Meshtastic.Button.Rounded" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/debug_last_messages"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/debugListView"
|
||||
app:layout_constraintEnd_toStartOf="@+id/clearButton"
|
||||
app:layout_constraintStart_toEndOf="@+id/closeButton"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,58 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/colorAdvancedBackground">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/quickChatSettingsToolbar"
|
||||
style="@style/MyToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@color/toolbarBackground"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:navigationIcon="?android:attr/homeAsUpIndicator">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/quickChatSettingsTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/quick_chat"
|
||||
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<androidx.compose.ui.platform.ComposeView
|
||||
android:id="@+id/quickChatSettingsView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/quickChatSettingsToolbar" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/quickChatSettingsCreateButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="16dp"
|
||||
android:clickable="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:srcCompat="@drawable/ic_twotone_add_24" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -115,7 +115,6 @@
|
|||
<ID>FinalNewline:PreferenceCategory.kt$com.geeksville.mesh.ui.components.PreferenceCategory.kt</ID>
|
||||
<ID>FinalNewline:PreviewParameterProviders.kt$com.geeksville.mesh.ui.preview.PreviewParameterProviders.kt</ID>
|
||||
<ID>FinalNewline:ProbeTableProvider.kt$com.geeksville.mesh.repository.usb.ProbeTableProvider.kt</ID>
|
||||
<ID>FinalNewline:QuickChatActionDao.kt$com.geeksville.mesh.database.dao.QuickChatActionDao.kt</ID>
|
||||
<ID>FinalNewline:QuickChatActionRepository.kt$com.geeksville.mesh.database.QuickChatActionRepository.kt</ID>
|
||||
<ID>FinalNewline:RadioNotConnectedException.kt$com.geeksville.mesh.service.RadioNotConnectedException.kt</ID>
|
||||
<ID>FinalNewline:RadioRepositoryModule.kt$com.geeksville.mesh.repository.radio.RadioRepositoryModule.kt</ID>
|
||||
|
@ -219,7 +218,7 @@
|
|||
<ID>MagicNumber:ContactsFragment.kt$ContactsFragment.ActionModeCallback$8</ID>
|
||||
<ID>MagicNumber:ContextServices.kt$33</ID>
|
||||
<ID>MagicNumber:DataPacket.kt$DataPacket.CREATOR$16</ID>
|
||||
<ID>MagicNumber:DebugFragment.kt$DebugFragment$3</ID>
|
||||
<ID>MagicNumber:DebugFragment.kt$3</ID>
|
||||
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$100</ID>
|
||||
<ID>MagicNumber:DeviceVersion.kt$DeviceVersion$10000</ID>
|
||||
<ID>MagicNumber:DownloadButton.kt$1.25f</ID>
|
||||
|
@ -262,7 +261,6 @@
|
|||
<ID>MagicNumber:MainActivity.kt$MainActivity$5</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$0.5f</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$1.3</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$1.5</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$1000</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$1024.0</ID>
|
||||
<ID>MagicNumber:MapFragment.kt$128205</ID>
|
||||
|
@ -341,14 +339,12 @@
|
|||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1000</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e-7</ID>
|
||||
<ID>MagicNumber:NodeInfo.kt$Position.Companion$1e7</ID>
|
||||
<ID>MagicNumber:NodeMenu.kt$3</ID>
|
||||
<ID>MagicNumber:PacketRepository.kt$PacketRepository$500</ID>
|
||||
<ID>MagicNumber:PacketResponseStateDialog.kt$100</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$21972</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$32809</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$6790</ID>
|
||||
<ID>MagicNumber:ProbeTableProvider.kt$ProbeTableProvider$9114</ID>
|
||||
<ID>MagicNumber:QuickChatSettingsFragment.kt$QuickChatSettingsFragment$3</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$10</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$100</ID>
|
||||
<ID>MagicNumber:SafeBluetooth.kt$SafeBluetooth$1000</ID>
|
||||
|
@ -577,7 +573,6 @@
|
|||
<ID>NewLineAtEndOfFile:PreferenceCategory.kt$com.geeksville.mesh.ui.components.PreferenceCategory.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:PreviewParameterProviders.kt$com.geeksville.mesh.ui.preview.PreviewParameterProviders.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:ProbeTableProvider.kt$com.geeksville.mesh.repository.usb.ProbeTableProvider.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:QuickChatActionDao.kt$com.geeksville.mesh.database.dao.QuickChatActionDao.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:QuickChatActionRepository.kt$com.geeksville.mesh.database.QuickChatActionRepository.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:RadioNotConnectedException.kt$com.geeksville.mesh.service.RadioNotConnectedException.kt</ID>
|
||||
<ID>NewLineAtEndOfFile:RadioRepositoryModule.kt$com.geeksville.mesh.repository.radio.RadioRepositoryModule.kt</ID>
|
||||
|
@ -602,7 +597,6 @@
|
|||
<ID>NoBlankLineBeforeRbrace:OnlineTileSourceAuth.kt$OnlineTileSourceAuth$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:PositionTest.kt$PositionTest$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:PreviewParameterProviders.kt$NodeInfoPreviewParameterProvider$ </ID>
|
||||
<ID>NoBlankLineBeforeRbrace:QuickChatActionDao.kt$QuickChatActionDao$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:AppIntroduction.kt$AppIntroduction$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:AppPrefs.kt$ </ID>
|
||||
<ID>NoConsecutiveBlankLines:BluetoothInterface.kt$ </ID>
|
||||
|
@ -637,7 +631,6 @@
|
|||
<ID>NoWildcardImports:MockInterface.kt$import com.geeksville.mesh.*</ID>
|
||||
<ID>NoWildcardImports:PreferenceFooter.kt$import androidx.compose.foundation.layout.*</ID>
|
||||
<ID>NoWildcardImports:PreferenceFooter.kt$import androidx.compose.material.*</ID>
|
||||
<ID>NoWildcardImports:QuickChatActionDao.kt$import androidx.room.*</ID>
|
||||
<ID>NoWildcardImports:SafeBluetooth.kt$import android.bluetooth.*</ID>
|
||||
<ID>NoWildcardImports:SafeBluetooth.kt$import kotlinx.coroutines.*</ID>
|
||||
<ID>NoWildcardImports:SettingsFragment.kt$import com.geeksville.mesh.android.*</ID>
|
||||
|
@ -761,7 +754,6 @@
|
|||
<ID>WildcardImport:MockInterface.kt$import com.geeksville.mesh.*</ID>
|
||||
<ID>WildcardImport:PreferenceFooter.kt$import androidx.compose.foundation.layout.*</ID>
|
||||
<ID>WildcardImport:PreferenceFooter.kt$import androidx.compose.material.*</ID>
|
||||
<ID>WildcardImport:QuickChatActionDao.kt$import androidx.room.*</ID>
|
||||
<ID>WildcardImport:SafeBluetooth.kt$import android.bluetooth.*</ID>
|
||||
<ID>WildcardImport:SafeBluetooth.kt$import kotlinx.coroutines.*</ID>
|
||||
<ID>WildcardImport:SettingsFragment.kt$import com.geeksville.mesh.android.*</ID>
|
||||
|
|
Ładowanie…
Reference in New Issue