add in app log viewer

This commit is contained in:
qimiko committed 2024-01-20 06:20:50 -07:00
1 parent 05eb3af243
commit 397cb9b9bd
8 files changed
+593 -18

No files matched your search

@@ -0,0 +1,275 @@
package com.geode.launcher
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
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.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowBack
import androidx.compose.material.icons.filled.Share
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import com.geode.launcher.log.LogLine
import com.geode.launcher.log.LogPriority
import com.geode.launcher.log.LogViewModel
import com.geode.launcher.ui.theme.GeodeLauncherTheme
import com.geode.launcher.ui.theme.LocalTheme
import com.geode.launcher.ui.theme.Theme
import com.geode.launcher.ui.theme.Typography
import com.geode.launcher.utils.PreferenceUtils
import kotlinx.coroutines.launch
class ApplicationLogsActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val themeOption by PreferenceUtils.useIntPreference(PreferenceUtils.Key.THEME)
val theme = Theme.fromInt(themeOption)
val backgroundOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.BLACK_BACKGROUND)
CompositionLocalProvider(LocalTheme provides theme) {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
ApplicationLogsScreen(onBackPressedDispatcher)
}
}
}
}
}
}
@Composable
fun getLogColor(priority: LogPriority): Color {
val isDark = LocalTheme.current != Theme.LIGHT
// i just copied these from my ide tbh
return if (isDark) {
when (priority) {
LogPriority.ERROR -> Color(0xffdf5151)
LogPriority.WARN -> Color(0xffbcb500)
LogPriority.FATAL -> Color(0xffb8b6fc)
else -> Color.Unspecified
}
} else {
when (priority) {
LogPriority.ERROR -> Color(0xffdf5151)
LogPriority.WARN -> Color(0xffeab700)
LogPriority.FATAL -> Color(0xffa142f4)
else -> Color.Unspecified
}
}
}
fun copyLogText(context: Context, logMessage: String) {
val clipboardManager =
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(
ClipData.newPlainText(
context.getString(R.string.application_log_copy_label),
logMessage
)
)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
Toast.makeText(
context,
context.getString(R.string.text_copied),
Toast.LENGTH_SHORT
).show()
}
}
@Composable
fun LogCard(logLine: LogLine, modifier: Modifier = Modifier) {
val context = LocalContext.current
Column(
modifier = modifier
.padding(8.dp)
.pointerInput(Unit) {
detectTapGestures(
onLongPress = {
copyLogText(context, logLine.asSimpleString)
}
)
},
) {
Text(
"[${logLine.priority.toChar()}] ${logLine.formattedTime}",
style = Typography.labelSmall,
color = getLogColor(logLine.priority),
fontFamily = FontFamily.Monospace
)
Text(
"[${logLine.tag}]: ${logLine.message}",
fontFamily = FontFamily.Monospace,
style = Typography.bodyMedium
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ApplicationLogsScreen(
onBackPressedDispatcher: OnBackPressedDispatcher?,
logViewModel: LogViewModel = viewModel()
) {
val logLines = logViewModel.lineState
val isLoading by logViewModel.isLoading.collectAsState()
val context = LocalContext.current
val coroutineScope = rememberCoroutineScope()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val listState = rememberLazyListState()
LaunchedEffect(logLines.size) {
listState.scrollToItem(logLines.size)
}
Scaffold(
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
Column {
TopAppBar(
navigationIcon = {
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
Icon(
imageVector = Icons.Filled.ArrowBack,
contentDescription = stringResource(R.string.back_icon_alt)
)
}
},
actions = {
IconButton(onClick = {
if (!logLines.isEmpty()) {
coroutineScope.launch {
val data = logViewModel.getLogData()
val sendIntent = Intent(Intent.ACTION_SEND).apply {
putExtra(Intent.EXTRA_TEXT, data)
type = "text/plain"
}
val shareIntent = Intent.createChooser(sendIntent, null)
context.startActivity(shareIntent)
}
}
}) {
Icon(
Icons.Filled.Share,
contentDescription = stringResource(R.string.application_logs_share)
)
}
IconButton(onClick = {
logViewModel.clearLogs()
onBackPressedDispatcher?.onBackPressed()
}) {
Icon(
painterResource(R.drawable.icon_delete),
contentDescription = stringResource(R.string.application_logs_delete)
)
}
},
title = {
Text(
stringResource(R.string.title_activity_application_logs),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
scrollBehavior = scrollBehavior,
)
if (isLoading) {
LinearProgressIndicator(
modifier = Modifier.fillMaxWidth()
)
}
}
}
) { innerPadding ->
LazyColumn(
Modifier
.padding(innerPadding)
.fillMaxWidth(),
verticalArrangement = Arrangement.SpaceEvenly,
state = listState
) {
if (!isLoading && logLines.isEmpty()) {
item {
Text(
stringResource(R.string.application_logs_no_logs),
textAlign = TextAlign.Center,
style = Typography.bodyLarge,
modifier = Modifier.fillMaxSize()
)
}
}
items(
logLines
) { line ->
LogCard(line)
Divider()
}
}
}
}
@Preview(showSystemUi = true)
@Composable
fun LogsPreview() {
GeodeLauncherTheme {
ApplicationLogsScreen(null)
}
}