forked from geode-sdk/android-launcher
crash logs activity is now composable
This commit is contained in:
4 files changed
+209
-121
No files matched your search
@@ -21,6 +21,8 @@ abstract class BaseDirectoryViewModel<T>(protected val baseDirectory: File) : Vi
|
|||||||
|
|
||||||
abstract fun getFileList(): List<T>
|
abstract fun getFileList(): List<T>
|
||||||
|
|
||||||
|
abstract fun getItemKey(item: T): Any
|
||||||
|
|
||||||
open fun clearAllFiles() {
|
open fun clearAllFiles() {
|
||||||
logJob?.cancel()
|
logJob?.cancel()
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ class CrashViewModel(crashDirectory: File) : BaseDirectoryViewModel<CrashDump>(c
|
|||||||
|
|
||||||
var hasIndicator = false
|
var hasIndicator = false
|
||||||
|
|
||||||
|
override fun getItemKey(item: CrashDump) = item.filename
|
||||||
|
|
||||||
override fun getFileList(): List<CrashDump> {
|
override fun getFileList(): List<CrashDump> {
|
||||||
val crashDirectory = baseDirectory
|
val crashDirectory = baseDirectory
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,196 @@
|
|||||||
|
package com.geode.launcher.preferences
|
||||||
|
|
||||||
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material3.DropdownMenu
|
||||||
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.geode.launcher.R
|
||||||
|
import com.geode.launcher.log.BaseDirectoryViewModel
|
||||||
|
import com.geode.launcher.ui.theme.Typography
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CrashLogsTopBar(
|
||||||
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
|
directoryViewModel: BaseDirectoryViewModel<*>,
|
||||||
|
scrollBehavior: TopAppBarScrollBehavior,
|
||||||
|
@StringRes titleId: Int,
|
||||||
|
additionalOptions: (@Composable (() -> Unit) -> Unit)? = null,
|
||||||
|
) {
|
||||||
|
val isLoading by directoryViewModel.isLoading.collectAsState()
|
||||||
|
var showMoreOptions by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Column {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_arrow_back),
|
||||||
|
contentDescription = stringResource(R.string.back_icon_alt)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { showMoreOptions = !showMoreOptions }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_more_vert),
|
||||||
|
contentDescription = stringResource(R.string.application_logs_more)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = showMoreOptions,
|
||||||
|
onDismissRequest = { showMoreOptions = false }
|
||||||
|
) {
|
||||||
|
DropdownMenuItem(
|
||||||
|
leadingIcon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_delete),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(stringResource(R.string.application_logs_delete))
|
||||||
|
}, onClick = {
|
||||||
|
directoryViewModel.clearAllFiles()
|
||||||
|
showMoreOptions = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
additionalOptions?.invoke {
|
||||||
|
showMoreOptions = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(
|
||||||
|
stringResource(titleId),
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
},
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> CrashLogsListing(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
directoryViewModel: BaseDirectoryViewModel<T>,
|
||||||
|
@StringRes noneAvailableId: Int,
|
||||||
|
logCard: @Composable (T, Shape) -> Unit,
|
||||||
|
) {
|
||||||
|
val logLines = directoryViewModel.lineState
|
||||||
|
val isLoading by directoryViewModel.isLoading.collectAsState()
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier,
|
||||||
|
verticalArrangement = Arrangement.SpaceEvenly,
|
||||||
|
) {
|
||||||
|
if (!isLoading && logLines.isEmpty()) {
|
||||||
|
item {
|
||||||
|
Text(
|
||||||
|
stringResource(noneAvailableId),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = Typography.bodyLarge,
|
||||||
|
modifier = Modifier.fillMaxSize().padding(8.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val itemsSize = logLines.size
|
||||||
|
|
||||||
|
itemsIndexed(
|
||||||
|
logLines,
|
||||||
|
key = { index, line -> directoryViewModel.getItemKey(line) }
|
||||||
|
) { index, line ->
|
||||||
|
val shape = RoundedCornerShape(
|
||||||
|
topStart = if (index == 0) 16.dp else 4.dp,
|
||||||
|
topEnd = if (index == 0) 16.dp else 4.dp,
|
||||||
|
bottomStart = if (index + 1 == itemsSize) 16.dp else 4.dp,
|
||||||
|
bottomEnd = if (index + 1 == itemsSize) 16.dp else 4.dp
|
||||||
|
)
|
||||||
|
|
||||||
|
logCard(line, shape)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun <T> DirectoryListingScreen(
|
||||||
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
|
directoryViewModel: BaseDirectoryViewModel<T>,
|
||||||
|
@StringRes titleId: Int,
|
||||||
|
@StringRes noneAvailableId: Int,
|
||||||
|
additionalOptions: (@Composable (() -> Unit) -> Unit)? = null,
|
||||||
|
logCard: @Composable (T, Shape) -> Unit,
|
||||||
|
) {
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
CrashLogsTopBar(
|
||||||
|
onBackPressedDispatcher,
|
||||||
|
directoryViewModel,
|
||||||
|
scrollBehavior,
|
||||||
|
titleId,
|
||||||
|
additionalOptions = additionalOptions
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) { innerPadding ->
|
||||||
|
CrashLogsListing(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(innerPadding)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
directoryViewModel,
|
||||||
|
noneAvailableId = noneAvailableId,
|
||||||
|
logCard = logCard
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,43 +15,28 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
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.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.offset
|
import androidx.compose.foundation.layout.offset
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.DropdownMenu
|
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.LinearProgressIndicator
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -63,7 +48,6 @@ import com.geode.launcher.log.CrashViewModel
|
|||||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||||
import com.geode.launcher.ui.theme.LocalTheme
|
import com.geode.launcher.ui.theme.LocalTheme
|
||||||
import com.geode.launcher.ui.theme.Theme
|
import com.geode.launcher.ui.theme.Theme
|
||||||
import com.geode.launcher.ui.theme.Typography
|
|
||||||
import com.geode.launcher.ui.theme.robotoMonoFamily
|
import com.geode.launcher.ui.theme.robotoMonoFamily
|
||||||
import com.geode.launcher.utils.LaunchUtils
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
@@ -180,59 +164,17 @@ fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Mo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CrashLogsScreen(
|
fun CrashLogsScreen(
|
||||||
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
crashViewModel: CrashViewModel = viewModel(factory = CrashViewModel.Factory)
|
crashViewModel: CrashViewModel = viewModel(factory = CrashViewModel.Factory)
|
||||||
) {
|
) {
|
||||||
val logLines = crashViewModel.lineState
|
DirectoryListingScreen(
|
||||||
val isLoading by crashViewModel.isLoading.collectAsState()
|
onBackPressedDispatcher,
|
||||||
|
crashViewModel,
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
titleId = R.string.title_activity_crash_logs,
|
||||||
|
noneAvailableId = R.string.application_crashes_no_dumps,
|
||||||
var showMoreOptions by remember { mutableStateOf(false) }
|
additionalOptions = { onDismiss ->
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
|
||||||
topBar = {
|
|
||||||
Column {
|
|
||||||
TopAppBar(
|
|
||||||
navigationIcon = {
|
|
||||||
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_arrow_back),
|
|
||||||
contentDescription = stringResource(R.string.back_icon_alt)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
IconButton(onClick = { showMoreOptions = !showMoreOptions }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_more_vert),
|
|
||||||
contentDescription = stringResource(R.string.application_logs_more)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
DropdownMenu(
|
|
||||||
expanded = showMoreOptions,
|
|
||||||
onDismissRequest = { showMoreOptions = false }
|
|
||||||
) {
|
|
||||||
DropdownMenuItem(
|
|
||||||
leadingIcon = {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_delete),
|
|
||||||
contentDescription = null
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(stringResource(R.string.application_logs_delete))
|
|
||||||
}, onClick = {
|
|
||||||
crashViewModel.clearAllFiles()
|
|
||||||
showMoreOptions = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val developerMode by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DEVELOPER_MODE)
|
val developerMode by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DEVELOPER_MODE)
|
||||||
if (developerMode) {
|
if (developerMode) {
|
||||||
if (crashViewModel.hasIndicator) {
|
if (crashViewModel.hasIndicator) {
|
||||||
@@ -247,7 +189,7 @@ fun CrashLogsScreen(
|
|||||||
Text(stringResource(R.string.application_crashes_clear_indicator))
|
Text(stringResource(R.string.application_crashes_clear_indicator))
|
||||||
}, onClick = {
|
}, onClick = {
|
||||||
crashViewModel.clearIndicator()
|
crashViewModel.clearIndicator()
|
||||||
showMoreOptions = false
|
onDismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -262,73 +204,19 @@ fun CrashLogsScreen(
|
|||||||
Text(stringResource(R.string.application_crashes_create_indicator))
|
Text(stringResource(R.string.application_crashes_create_indicator))
|
||||||
}, onClick = {
|
}, onClick = {
|
||||||
crashViewModel.createIndicator()
|
crashViewModel.createIndicator()
|
||||||
showMoreOptions = false
|
onDismiss()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
) { line, shape ->
|
||||||
title = {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.title_activity_crash_logs),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
},
|
|
||||||
scrollBehavior = scrollBehavior,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isLoading) {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) { innerPadding ->
|
|
||||||
LazyColumn(
|
|
||||||
Modifier
|
|
||||||
.padding(innerPadding)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
verticalArrangement = Arrangement.SpaceEvenly,
|
|
||||||
) {
|
|
||||||
if (!isLoading && logLines.isEmpty()) {
|
|
||||||
item {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.application_crashes_no_dumps),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
style = Typography.bodyLarge,
|
|
||||||
modifier = Modifier.fillMaxSize().padding(8.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val itemsSize = logLines.size
|
|
||||||
|
|
||||||
itemsIndexed(
|
|
||||||
logLines,
|
|
||||||
key = { index, line -> line.filename }
|
|
||||||
) { index, line ->
|
|
||||||
val shape = RoundedCornerShape(
|
|
||||||
topStart = if (index == 0) 16.dp else 4.dp,
|
|
||||||
topEnd = if (index == 0) 16.dp else 4.dp,
|
|
||||||
bottomStart = if (index + 1 == itemsSize) 16.dp else 4.dp,
|
|
||||||
bottomEnd = if (index + 1 == itemsSize) 16.dp else 4.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
CrashCard(
|
CrashCard(
|
||||||
line,
|
line,
|
||||||
crashViewModel,
|
crashViewModel,
|
||||||
modifier = Modifier.clip(shape)
|
modifier = Modifier.clip(shape)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
|
||||||
Spacer(Modifier.height(4.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(showSystemUi = true)
|
@Preview(showSystemUi = true)
|
||||||
|
|||||||
Reference in new issue
Block a user