kopia lustrzana https://github.com/meshtastic/Meshtastic-Android
refactor: migrate `DebugFragment` RecyclerView to Compose
rodzic
c7a3488a78
commit
a543bcbfcd
|
@ -1,85 +0,0 @@
|
||||||
package com.geeksville.mesh.ui
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.text.SpannedString
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.text.toSpannable
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.geeksville.mesh.R
|
|
||||||
import com.geeksville.mesh.database.entity.MeshLog
|
|
||||||
import java.text.DateFormat
|
|
||||||
import java.util.Date
|
|
||||||
|
|
||||||
class DebugAdapter internal constructor(
|
|
||||||
context: Context
|
|
||||||
) : RecyclerView.Adapter<DebugAdapter.DebugViewHolder>() {
|
|
||||||
|
|
||||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
|
||||||
private val colorAnnotation = ContextCompat.getColor(context, R.color.colorAnnotation)
|
|
||||||
private var logs = emptyList<MeshLog>()
|
|
||||||
|
|
||||||
private val timeFormat: DateFormat =
|
|
||||||
DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM)
|
|
||||||
|
|
||||||
inner class DebugViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
|
||||||
val logTypeView: TextView = itemView.findViewById(R.id.type)
|
|
||||||
val logDateReceivedView: TextView = itemView.findViewById(R.id.dateReceived)
|
|
||||||
val logRawMessage: TextView = itemView.findViewById(R.id.rawMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DebugViewHolder {
|
|
||||||
val itemView = inflater.inflate(R.layout.adapter_debug_layout, parent, false)
|
|
||||||
return DebugViewHolder(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: DebugViewHolder, position: Int) {
|
|
||||||
val current = logs[position]
|
|
||||||
holder.logTypeView.text = current.message_type
|
|
||||||
holder.logRawMessage.text = annotateMessage(current)
|
|
||||||
val date = Date(current.received_date)
|
|
||||||
holder.logDateReceivedView.text = timeFormat.format(date)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Enhance the raw message by visually distinguishing the annotations prior to when
|
|
||||||
* the data was added to the database.
|
|
||||||
*
|
|
||||||
* @see com.geeksville.mesh.ui.DebugFragment.annotateMeshLogs
|
|
||||||
*/
|
|
||||||
private fun annotateMessage(current: MeshLog): CharSequence {
|
|
||||||
val spannable = current.raw_message.toSpannable()
|
|
||||||
REGEX_ANNOTATED_NODE_ID.findAll(spannable).toList().reversed().forEach {
|
|
||||||
spannable.setSpan(
|
|
||||||
android.text.style.StyleSpan(android.graphics.Typeface.ITALIC),
|
|
||||||
it.range.first,
|
|
||||||
it.range.last + 1,
|
|
||||||
SpannedString.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
||||||
)
|
|
||||||
spannable.setSpan(
|
|
||||||
android.text.style.ForegroundColorSpan(colorAnnotation),
|
|
||||||
it.range.first,
|
|
||||||
it.range.last + 1,
|
|
||||||
SpannedString.SPAN_EXCLUSIVE_EXCLUSIVE
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return spannable
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun setLogs(logs: List<MeshLog>) {
|
|
||||||
this.logs = logs
|
|
||||||
notifyDataSetChanged()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount() = logs.size
|
|
||||||
|
|
||||||
private companion object {
|
|
||||||
/**
|
|
||||||
* Regex to match the node ID annotations in the MeshLog raw message text.
|
|
||||||
*/
|
|
||||||
val REGEX_ANNOTATED_NODE_ID = Regex("\\(![0-9a-fA-F]{8}\\)$", RegexOption.MULTILINE)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,24 +1,51 @@
|
||||||
package com.geeksville.mesh.ui
|
package com.geeksville.mesh.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
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.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.asLiveData
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.geeksville.mesh.CoroutineDispatchers
|
|
||||||
import com.geeksville.mesh.R
|
import com.geeksville.mesh.R
|
||||||
import com.geeksville.mesh.database.entity.MeshLog
|
import com.geeksville.mesh.database.entity.MeshLog
|
||||||
import com.geeksville.mesh.databinding.FragmentDebugBinding
|
import com.geeksville.mesh.databinding.FragmentDebugBinding
|
||||||
import com.geeksville.mesh.model.DebugViewModel
|
import com.geeksville.mesh.model.DebugViewModel
|
||||||
|
import com.geeksville.mesh.ui.theme.AppTheme
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import java.text.DateFormat
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class DebugFragment : Fragment() {
|
class DebugFragment : Fragment() {
|
||||||
|
@ -30,9 +57,6 @@ class DebugFragment : Fragment() {
|
||||||
|
|
||||||
private val model: DebugViewModel by viewModels()
|
private val model: DebugViewModel by viewModels()
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var dispatchers: CoroutineDispatchers
|
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
|
@ -43,11 +67,6 @@ class DebugFragment : Fragment() {
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
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 {
|
binding.clearButton.setOnClickListener {
|
||||||
model.deleteAllLogs()
|
model.deleteAllLogs()
|
||||||
|
@ -56,12 +75,25 @@ class DebugFragment : Fragment() {
|
||||||
binding.closeButton.setOnClickListener {
|
binding.closeButton.setOnClickListener {
|
||||||
parentFragmentManager.popBackStack()
|
parentFragmentManager.popBackStack()
|
||||||
}
|
}
|
||||||
model.meshLog
|
|
||||||
.map(this::annotateMeshLogs)
|
binding.debugListView.setContent {
|
||||||
.flowOn(dispatchers.default)
|
val listState = rememberLazyListState()
|
||||||
.asLiveData()
|
val logs by model.meshLog.collectAsStateWithLifecycle()
|
||||||
.observe(viewLifecycleOwner) { logs ->
|
|
||||||
logs?.let { adapter.setLogs(it) }
|
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)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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> {
|
private fun annotateMeshLog(meshLog: MeshLog): MeshLog {
|
||||||
return logs.map { meshLog ->
|
val annotated = when (meshLog.message_type) {
|
||||||
val annotated = when (meshLog.message_type) {
|
"Packet" -> {
|
||||||
"Packet" -> {
|
meshLog.meshPacket?.let { packet ->
|
||||||
meshLog.meshPacket?.let { packet ->
|
annotateRawMessage(meshLog.raw_message, packet.from, packet.to)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
if (annotated == null) {
|
|
||||||
meshLog
|
"NodeInfo" -> {
|
||||||
} else {
|
meshLog.nodeInfo?.let { nodeInfo ->
|
||||||
meshLog.copy(raw_message = annotated)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -133,4 +166,98 @@ class DebugFragment : Fragment() {
|
||||||
private fun Int.asNodeId(): String {
|
private fun Int.asNodeId(): String {
|
||||||
return "!%08x".format(Locale.getDefault(), this)
|
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",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,86 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:id="@+id/adapterDebugLayout"
|
|
||||||
style="@style/Widget.App.CardView"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_margin="4dp">
|
|
||||||
|
|
||||||
<androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/type"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/cloudDownloadIcon"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/cloudDownloadIcon"
|
|
||||||
tools:text="NodeInfo" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/dateReceived"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@+id/cloudDownloadIcon"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@+id/cloudDownloadIcon"
|
|
||||||
tools:text="9/27/20 21:00:58" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/rawMessage"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:layout_marginBottom="8dp"
|
|
||||||
android:fontFamily="monospace"
|
|
||||||
android:singleLine="false"
|
|
||||||
android:textAppearance="@style/TextAppearance.AppCompat.Small"
|
|
||||||
android:textIsSelectable="true"
|
|
||||||
android:textSize="8sp"
|
|
||||||
android:typeface="monospace"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/cloudDownloadIcon"
|
|
||||||
app:layout_constraintVertical_weight="1"
|
|
||||||
tools:text="# com.geeksville.mesh.MeshProtos$MeshPacket@1b1ea594\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
|
|
||||||
from: -1409794164\n
|
|
||||||
hop_limit: 3\n
|
|
||||||
id: 1737414295\n
|
|
||||||
rx_snr: 9.5\n
|
|
||||||
rx_time: 316400569\n
|
|
||||||
to: -1409790708" />
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/cloudDownloadIcon"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:alpha="0.4"
|
|
||||||
app:layout_constraintEnd_toStartOf="@+id/dateReceived"
|
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
|
||||||
app:srcCompat="@drawable/cloud_download_outline_24"
|
|
||||||
android:contentDescription="TODO"
|
|
||||||
app:tint="@color/colorIconTint" />
|
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
||||||
</com.google.android.material.card.MaterialCardView>
|
|
||||||
|
|
|
@ -7,19 +7,15 @@
|
||||||
android:background="@color/colorAdvancedBackground"
|
android:background="@color/colorAdvancedBackground"
|
||||||
>
|
>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<androidx.compose.ui.platform.ComposeView
|
||||||
android:id="@+id/debug_recyclerview"
|
android:id="@+id/debugListView"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:scrollbarAlwaysDrawVerticalTrack="true"
|
|
||||||
android:scrollbars="vertical"
|
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/clearButton"
|
app:layout_constraintTop_toBottomOf="@+id/clearButton" />
|
||||||
tools:itemCount="8"
|
|
||||||
tools:listitem="@layout/adapter_debug_layout" />
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/clearButton"
|
android:id="@+id/clearButton"
|
||||||
|
|
Ładowanie…
Reference in New Issue