diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/PreferenceScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/PreferenceScreen.kt index e74698bd..095cbd18 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/PreferenceScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/preferences/PreferenceScreen.kt @@ -12,6 +12,8 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn 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.rounded.ArrowBack import androidx.compose.material.icons.rounded.HelpOutline @@ -48,6 +50,7 @@ fun PreferenceScreen( floatingActionButton: @Composable () -> Unit = {}, topBarActions: @Composable RowScope.() -> Unit = {}, helpUrl: String? = null, + lazyColumnState: LazyListState = rememberLazyListState(), content: LazyListScope.() -> Unit, ) { PreferenceScreen( @@ -62,6 +65,7 @@ fun PreferenceScreen( floatingActionButton = floatingActionButton, topBarActions = topBarActions, helpUrl = helpUrl, + lazyColumnState = lazyColumnState, content = content ) } @@ -72,6 +76,7 @@ fun PreferenceScreen( floatingActionButton: @Composable () -> Unit = {}, topBarActions: @Composable RowScope.() -> Unit = {}, helpUrl: String? = null, + lazyColumnState: LazyListState = rememberLazyListState(), content: LazyListScope.() -> Unit, ) { val navController = LocalNavController.current @@ -149,6 +154,7 @@ fun PreferenceScreen( .fillMaxSize() .nestedScroll(nestedScrollConnection) .padding(it), + state = lazyColumnState, content = content, ) } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/log/LogScreen.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/log/LogScreen.kt index 476e160c..11056fa7 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/settings/log/LogScreen.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/settings/log/LogScreen.kt @@ -1,13 +1,18 @@ package de.mm20.launcher2.ui.settings.log 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.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState 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.Error 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.IconButton import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SmallFloatingActionButton +import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -45,12 +52,15 @@ import java.util.regex.Pattern @Composable fun LogScreen() { var lines by remember { mutableStateOf(emptyList()) } + val listState = rememberLazyListState() + LaunchedEffect(null) { val process = Runtime.getRuntime().exec("/system/bin/logcat -v time") val pattern = Pattern.compile( "^(\\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) { val inputStream = process.inputStream.bufferedReader() @@ -108,31 +118,56 @@ fun LogScreen() { }) { 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) { 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) { - val contentColor = when(it.level) { + val contentColor = when (it.level) { "E" -> MaterialTheme.colorScheme.onErrorContainer "W" -> MaterialTheme.colorScheme.onPrimaryContainer "D" -> MaterialTheme.colorScheme.onSurfaceVariant else -> MaterialTheme.colorScheme.onSurface } - val bgColor = when(it.level) { + val bgColor = when (it.level) { "E" -> MaterialTheme.colorScheme.errorContainer "W" -> MaterialTheme.colorScheme.primaryContainer "D" -> MaterialTheme.colorScheme.surfaceVariant else -> MaterialTheme.colorScheme.surface } - Column(modifier = Modifier - .fillMaxWidth() - .background(bgColor) - .padding(16.dp)) { + Column( + modifier = Modifier + .fillMaxWidth() + .background(bgColor) + .padding(16.dp) + ) { Row(verticalAlignment = Alignment.CenterVertically) { Icon( - when(it.level) { + when (it.level) { "E" -> Icons.Rounded.Error "W" -> Icons.Rounded.Warning "D" -> Icons.Rounded.BugReport @@ -169,4 +204,4 @@ data class FormattedLogcatLine( val message: String, ) : LogcatLine -data class RawLogcatLine(val line: String): LogcatLine \ No newline at end of file +data class RawLogcatLine(val line: String) : LogcatLine \ No newline at end of file