Log screen: add scroll to end button
This commit is contained in:
parent
633cc0214d
commit
9d909e718c
@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.rounded.ArrowBack
|
import androidx.compose.material.icons.rounded.ArrowBack
|
||||||
import androidx.compose.material.icons.rounded.HelpOutline
|
import androidx.compose.material.icons.rounded.HelpOutline
|
||||||
@ -48,6 +50,7 @@ fun PreferenceScreen(
|
|||||||
floatingActionButton: @Composable () -> Unit = {},
|
floatingActionButton: @Composable () -> Unit = {},
|
||||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
helpUrl: String? = null,
|
helpUrl: String? = null,
|
||||||
|
lazyColumnState: LazyListState = rememberLazyListState(),
|
||||||
content: LazyListScope.() -> Unit,
|
content: LazyListScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
PreferenceScreen(
|
PreferenceScreen(
|
||||||
@ -62,6 +65,7 @@ fun PreferenceScreen(
|
|||||||
floatingActionButton = floatingActionButton,
|
floatingActionButton = floatingActionButton,
|
||||||
topBarActions = topBarActions,
|
topBarActions = topBarActions,
|
||||||
helpUrl = helpUrl,
|
helpUrl = helpUrl,
|
||||||
|
lazyColumnState = lazyColumnState,
|
||||||
content = content
|
content = content
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -72,6 +76,7 @@ fun PreferenceScreen(
|
|||||||
floatingActionButton: @Composable () -> Unit = {},
|
floatingActionButton: @Composable () -> Unit = {},
|
||||||
topBarActions: @Composable RowScope.() -> Unit = {},
|
topBarActions: @Composable RowScope.() -> Unit = {},
|
||||||
helpUrl: String? = null,
|
helpUrl: String? = null,
|
||||||
|
lazyColumnState: LazyListState = rememberLazyListState(),
|
||||||
content: LazyListScope.() -> Unit,
|
content: LazyListScope.() -> Unit,
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
@ -149,6 +154,7 @@ fun PreferenceScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(nestedScrollConnection)
|
.nestedScroll(nestedScrollConnection)
|
||||||
.padding(it),
|
.padding(it),
|
||||||
|
state = lazyColumnState,
|
||||||
content = content,
|
content = content,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,18 @@
|
|||||||
package de.mm20.launcher2.ui.settings.log
|
package de.mm20.launcher2.ui.settings.log
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.rounded.ArrowDownward
|
||||||
import androidx.compose.material.icons.rounded.BugReport
|
import androidx.compose.material.icons.rounded.BugReport
|
||||||
import androidx.compose.material.icons.rounded.Error
|
import androidx.compose.material.icons.rounded.Error
|
||||||
import androidx.compose.material.icons.rounded.Info
|
import androidx.compose.material.icons.rounded.Info
|
||||||
@ -16,6 +21,8 @@ import androidx.compose.material.icons.rounded.Warning
|
|||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.SmallFloatingActionButton
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -45,12 +52,15 @@ import java.util.regex.Pattern
|
|||||||
@Composable
|
@Composable
|
||||||
fun LogScreen() {
|
fun LogScreen() {
|
||||||
var lines by remember { mutableStateOf(emptyList<LogcatLine>()) }
|
var lines by remember { mutableStateOf(emptyList<LogcatLine>()) }
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(null) {
|
LaunchedEffect(null) {
|
||||||
val process = Runtime.getRuntime().exec("/system/bin/logcat -v time")
|
val process = Runtime.getRuntime().exec("/system/bin/logcat -v time")
|
||||||
|
|
||||||
val pattern = Pattern.compile(
|
val pattern = Pattern.compile(
|
||||||
"^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" + /* timestamp [1] */
|
"^(\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}.\\d{3})\\s+" + /* timestamp [1] */
|
||||||
"(\\w)/(.+?)\\(\\s*(\\d+)\\): (.*)$") /* level, tag, pid, msg [2-5] */
|
"(\\w)/(.+?)\\(\\s*(\\d+)\\): (.*)$"
|
||||||
|
) /* level, tag, pid, msg [2-5] */
|
||||||
|
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
val inputStream = process.inputStream.bufferedReader()
|
val inputStream = process.inputStream.bufferedReader()
|
||||||
@ -108,31 +118,56 @@ fun LogScreen() {
|
|||||||
}) {
|
}) {
|
||||||
Icon(Icons.Rounded.Share, contentDescription = null)
|
Icon(Icons.Rounded.Share, contentDescription = null)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
floatingActionButton = {
|
||||||
|
AnimatedVisibility(
|
||||||
|
listState.canScrollForward,
|
||||||
|
enter = scaleIn(),
|
||||||
|
exit = scaleOut(),
|
||||||
|
) {
|
||||||
|
SmallFloatingActionButton(
|
||||||
|
containerColor = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
onClick = {
|
||||||
|
scope.launch {
|
||||||
|
listState.animateScrollToItem(lines.lastIndex)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Rounded.ArrowDownward, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lazyColumnState = listState,
|
||||||
) {
|
) {
|
||||||
items(lines) {
|
items(lines) {
|
||||||
if (it is RawLogcatLine) {
|
if (it is RawLogcatLine) {
|
||||||
Text(modifier = Modifier.padding(16.dp), text = it.line ?: "", style = MaterialTheme.typography.bodySmall)
|
Text(
|
||||||
|
modifier = Modifier.padding(16.dp),
|
||||||
|
text = it.line ?: "",
|
||||||
|
style = MaterialTheme.typography.bodySmall
|
||||||
|
)
|
||||||
} else if (it is FormattedLogcatLine) {
|
} else if (it is FormattedLogcatLine) {
|
||||||
val contentColor = when(it.level) {
|
val contentColor = when (it.level) {
|
||||||
"E" -> MaterialTheme.colorScheme.onErrorContainer
|
"E" -> MaterialTheme.colorScheme.onErrorContainer
|
||||||
"W" -> MaterialTheme.colorScheme.onPrimaryContainer
|
"W" -> MaterialTheme.colorScheme.onPrimaryContainer
|
||||||
"D" -> MaterialTheme.colorScheme.onSurfaceVariant
|
"D" -> MaterialTheme.colorScheme.onSurfaceVariant
|
||||||
else -> MaterialTheme.colorScheme.onSurface
|
else -> MaterialTheme.colorScheme.onSurface
|
||||||
}
|
}
|
||||||
val bgColor = when(it.level) {
|
val bgColor = when (it.level) {
|
||||||
"E" -> MaterialTheme.colorScheme.errorContainer
|
"E" -> MaterialTheme.colorScheme.errorContainer
|
||||||
"W" -> MaterialTheme.colorScheme.primaryContainer
|
"W" -> MaterialTheme.colorScheme.primaryContainer
|
||||||
"D" -> MaterialTheme.colorScheme.surfaceVariant
|
"D" -> MaterialTheme.colorScheme.surfaceVariant
|
||||||
else -> MaterialTheme.colorScheme.surface
|
else -> MaterialTheme.colorScheme.surface
|
||||||
}
|
}
|
||||||
Column(modifier = Modifier
|
Column(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.background(bgColor)
|
.fillMaxWidth()
|
||||||
.padding(16.dp)) {
|
.background(bgColor)
|
||||||
|
.padding(16.dp)
|
||||||
|
) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
Icon(
|
Icon(
|
||||||
when(it.level) {
|
when (it.level) {
|
||||||
"E" -> Icons.Rounded.Error
|
"E" -> Icons.Rounded.Error
|
||||||
"W" -> Icons.Rounded.Warning
|
"W" -> Icons.Rounded.Warning
|
||||||
"D" -> Icons.Rounded.BugReport
|
"D" -> Icons.Rounded.BugReport
|
||||||
@ -169,4 +204,4 @@ data class FormattedLogcatLine(
|
|||||||
val message: String,
|
val message: String,
|
||||||
) : LogcatLine
|
) : LogcatLine
|
||||||
|
|
||||||
data class RawLogcatLine(val line: String): LogcatLine
|
data class RawLogcatLine(val line: String) : LogcatLine
|
||||||
Loading…
x
Reference in New Issue
Block a user