mirror of
https://github.com/geode-sdk/android-launcher.git
synced 2026-10-22 16:42:38 +00:00
add crash dump viewer
This commit is contained in:
5 files changed
+450
-12
No files matched your search
@@ -0,0 +1,115 @@
|
|||||||
|
package com.geode.launcher.log
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.lifecycle.viewmodel.initializer
|
||||||
|
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||||
|
import com.geode.launcher.utils.LaunchUtils
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import java.io.File
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.Instant
|
||||||
|
|
||||||
|
data class CrashDump @OptIn(ExperimentalTime::class) constructor(
|
||||||
|
val filename: String,
|
||||||
|
val lastModified: Instant
|
||||||
|
)
|
||||||
|
|
||||||
|
class CrashViewModel(private val application: Application) : ViewModel() {
|
||||||
|
companion object {
|
||||||
|
val Factory: ViewModelProvider.Factory = viewModelFactory {
|
||||||
|
initializer {
|
||||||
|
val application = this[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY] as Application
|
||||||
|
|
||||||
|
CrashViewModel(
|
||||||
|
application = application
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _lineState = mutableStateListOf<CrashDump>()
|
||||||
|
val lineState = _lineState
|
||||||
|
|
||||||
|
private val _isLoading = MutableStateFlow(false)
|
||||||
|
val isLoading = _isLoading.asStateFlow()
|
||||||
|
|
||||||
|
private var failedToLoad = false
|
||||||
|
|
||||||
|
private var logJob: Job? = null
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
|
private fun getCrashList(): List<CrashDump> {
|
||||||
|
val crashDirectory = LaunchUtils.getCrashDirectory(application)
|
||||||
|
if (!crashDirectory.exists()) {
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
val children = crashDirectory.listFiles {
|
||||||
|
// ignore indicator files (including old file)
|
||||||
|
_, name -> name != LaunchUtils.CRASH_INDICATOR_NAME && name != "last-pid"
|
||||||
|
} ?: return emptyList()
|
||||||
|
|
||||||
|
return children
|
||||||
|
.map {
|
||||||
|
CrashDump(
|
||||||
|
filename = it.name,
|
||||||
|
lastModified = Instant.fromEpochMilliseconds(it.lastModified())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.sortedByDescending { it.lastModified }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearCrashes() {
|
||||||
|
logJob?.cancel()
|
||||||
|
|
||||||
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val crashDirectory = LaunchUtils.getCrashDirectory(application)
|
||||||
|
if (crashDirectory.exists()) {
|
||||||
|
val files = crashDirectory.listFiles()
|
||||||
|
files?.forEach {
|
||||||
|
it.delete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_lineState.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadLogs() {
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
|
logJob?.cancel()
|
||||||
|
logJob = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
val lines = getCrashList()
|
||||||
|
|
||||||
|
_lineState.clear()
|
||||||
|
_lineState.addAll(lines)
|
||||||
|
|
||||||
|
_isLoading.value = false
|
||||||
|
|
||||||
|
logJob = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCrash(filename: String) {
|
||||||
|
logJob?.cancel()
|
||||||
|
|
||||||
|
val crashPath = File(LaunchUtils.getCrashDirectory(application), filename)
|
||||||
|
if (crashPath.exists()) {
|
||||||
|
crashPath.delete()
|
||||||
|
loadLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
loadLogs()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,300 @@
|
|||||||
|
package com.geode.launcher.preferences
|
||||||
|
|
||||||
|
import android.content.ActivityNotFoundException
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.DocumentsContract
|
||||||
|
import android.widget.Toast
|
||||||
|
import java.text.DateFormat
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.enableEdgeToEdge
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
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.offset
|
||||||
|
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.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.material.icons.filled.MoreVert
|
||||||
|
import androidx.compose.material.icons.filled.Share
|
||||||
|
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.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.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.draw.clip
|
||||||
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
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.R
|
||||||
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
|
import com.geode.launcher.log.CrashDump
|
||||||
|
import com.geode.launcher.log.CrashViewModel
|
||||||
|
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.LaunchUtils
|
||||||
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Date
|
||||||
|
import kotlin.time.ExperimentalTime
|
||||||
|
import kotlin.time.toJavaInstant
|
||||||
|
|
||||||
|
class CrashLogsActivity : ComponentActivity() {
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
enableEdgeToEdge()
|
||||||
|
|
||||||
|
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)
|
||||||
|
val dynamicColorOption by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DISABLE_USER_THEME)
|
||||||
|
|
||||||
|
CompositionLocalProvider(LocalTheme provides theme) {
|
||||||
|
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption, dynamicColor = !dynamicColorOption) {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.colorScheme.background
|
||||||
|
) {
|
||||||
|
CrashLogsScreen(onBackPressedDispatcher)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun shareCrash(context: Context, filename: String) {
|
||||||
|
val baseDirectory = LaunchUtils.getBaseDirectory(context)
|
||||||
|
val crashPath = File(LaunchUtils.getCrashDirectory(context), filename)
|
||||||
|
|
||||||
|
if (!crashPath.exists()) {
|
||||||
|
Toast.makeText(context, R.string.launcher_error_export_missing, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val providerLocation = crashPath.toRelativeString(baseDirectory)
|
||||||
|
val documentsPath = "${UserDirectoryProvider.ROOT}${providerLocation}"
|
||||||
|
|
||||||
|
val uri = DocumentsContract.buildDocumentUri("${context.packageName}.user", documentsPath)
|
||||||
|
|
||||||
|
val shareIntent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
putExtra(Intent.EXTRA_STREAM, uri)
|
||||||
|
type = "application/octet-stream"
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
context.startActivity(shareIntent)
|
||||||
|
} catch (_: ActivityNotFoundException) {
|
||||||
|
Toast.makeText(context, R.string.no_activity_found, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTime::class)
|
||||||
|
@Composable
|
||||||
|
fun CrashCard(crashDump: CrashDump, crashViewModel: CrashViewModel, modifier: Modifier = Modifier) {
|
||||||
|
Row(modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 1.dp, horizontal = 8.dp)
|
||||||
|
.then(modifier)
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
|
.padding(vertical = 8.dp, horizontal = 8.dp)
|
||||||
|
) {
|
||||||
|
Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.offset(x = 4.dp)) {
|
||||||
|
Text(crashDump.filename, fontFamily = FontFamily.Monospace)
|
||||||
|
|
||||||
|
val formattedLastModified = remember {
|
||||||
|
DateFormat.getDateTimeInstance()
|
||||||
|
.format(
|
||||||
|
Date.from(crashDump.lastModified.toJavaInstant())
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
Text(formattedLastModified, style = MaterialTheme.typography.labelMedium)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.weight(1.0f))
|
||||||
|
|
||||||
|
Row {
|
||||||
|
val context = LocalContext.current
|
||||||
|
IconButton(onClick = {
|
||||||
|
shareCrash(context, crashDump.filename)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Share,
|
||||||
|
contentDescription = stringResource(R.string.application_logs_share)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = {
|
||||||
|
crashViewModel.removeCrash(crashDump.filename)
|
||||||
|
}) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.Delete,
|
||||||
|
contentDescription = stringResource(R.string.application_crashes_delete_one)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CrashLogsScreen(
|
||||||
|
onBackPressedDispatcher: OnBackPressedDispatcher?,
|
||||||
|
crashViewModel: CrashViewModel = viewModel(factory = CrashViewModel.Factory)
|
||||||
|
) {
|
||||||
|
val logLines = crashViewModel.lineState
|
||||||
|
val isLoading by crashViewModel.isLoading.collectAsState()
|
||||||
|
|
||||||
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
|
||||||
|
|
||||||
|
var showMoreOptions by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||||||
|
topBar = {
|
||||||
|
Column {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = { onBackPressedDispatcher?.onBackPressed() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||||
|
contentDescription = stringResource(R.string.back_icon_alt)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
IconButton(onClick = { showMoreOptions = !showMoreOptions }) {
|
||||||
|
Icon(
|
||||||
|
Icons.Filled.MoreVert,
|
||||||
|
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.clearCrashes()
|
||||||
|
showMoreOptions = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
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(
|
||||||
|
line,
|
||||||
|
crashViewModel,
|
||||||
|
modifier = Modifier.clip(shape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(showSystemUi = true)
|
||||||
|
@Composable
|
||||||
|
fun CrashLogsPreview() {
|
||||||
|
GeodeLauncherTheme {
|
||||||
|
CrashLogsScreen(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -443,18 +443,32 @@ fun SettingsScreen(
|
|||||||
val developerModeEnabled by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DEVELOPER_MODE)
|
val developerModeEnabled by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DEVELOPER_MODE)
|
||||||
|
|
||||||
OptionsGroup(stringResource(R.string.preference_category_developer)) {
|
OptionsGroup(stringResource(R.string.preference_category_developer)) {
|
||||||
if (developerModeEnabled) {
|
if (developerModeEnabled) {
|
||||||
OptionsButton(
|
OptionsButton(
|
||||||
title = stringResource(R.string.preference_open_developer_options),
|
title = stringResource(R.string.preference_open_developer_options),
|
||||||
icon = {
|
icon = {
|
||||||
Icon(
|
Icon(
|
||||||
painterResource(R.drawable.icon_data_object),
|
painterResource(R.drawable.icon_data_object),
|
||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = { onOpenDeveloperOptions(context) }
|
onClick = { onOpenDeveloperOptions(context) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
OptionsButton(
|
||||||
|
title = stringResource(R.string.preferences_view_crashes),
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_bug_report),
|
||||||
|
contentDescription = null
|
||||||
)
|
)
|
||||||
|
},
|
||||||
|
onClick = {
|
||||||
|
val launchIntent = Intent(context, CrashLogsActivity::class.java)
|
||||||
|
context.startActivity(launchIntent)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionsGroup(stringResource(R.string.preference_category_about)) {
|
OptionsGroup(stringResource(R.string.preference_category_about)) {
|
||||||
@@ -523,6 +537,8 @@ fun SettingsScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.height(4.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -73,9 +73,9 @@ object LaunchUtils {
|
|||||||
return File(getBaseDirectory(context), "save")
|
return File(getBaseDirectory(context), "save")
|
||||||
}
|
}
|
||||||
|
|
||||||
private const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
const val CRASH_INDICATOR_NAME = "lastSessionDidCrash"
|
||||||
|
|
||||||
private fun getCrashDirectory(context: Context): File {
|
fun getCrashDirectory(context: Context): File {
|
||||||
val base = getBaseDirectory(context)
|
val base = getBaseDirectory(context)
|
||||||
return File(base, "game/geode/crashlogs/")
|
return File(base, "game/geode/crashlogs/")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,6 +129,7 @@
|
|||||||
<string name="preferences_copy_external_button">Copy external files directory</string>
|
<string name="preferences_copy_external_button">Copy external files directory</string>
|
||||||
<string name="preferences_open_file_manager">Open file manager</string>
|
<string name="preferences_open_file_manager">Open file manager</string>
|
||||||
<string name="preferences_view_logs">View application logs</string>
|
<string name="preferences_view_logs">View application logs</string>
|
||||||
|
<string name="preferences_view_crashes">View crash dumps</string>
|
||||||
|
|
||||||
<string name="preference_theme_name">Theme</string>
|
<string name="preference_theme_name">Theme</string>
|
||||||
<string name="preference_theme_select">Select theme</string>
|
<string name="preference_theme_select">Select theme</string>
|
||||||
@@ -250,6 +251,7 @@
|
|||||||
<string name="application_logs_crash_only">Show crash buffer</string>
|
<string name="application_logs_crash_only">Show crash buffer</string>
|
||||||
<string name="application_logs_crash_only_enabled_alt">Enabled</string>
|
<string name="application_logs_crash_only_enabled_alt">Enabled</string>
|
||||||
|
|
||||||
|
<!-- DeveloperSettingsActivity strings -->
|
||||||
<string name="title_activity_developer_settings">Developer Settings</string>
|
<string name="title_activity_developer_settings">Developer Settings</string>
|
||||||
<string name="preference_developer_mode">Enable developer options</string>
|
<string name="preference_developer_mode">Enable developer options</string>
|
||||||
<string name="preference_developer_mode_about">Developer mode enables some options that may make the game unstable. Please do not modify them unless you know what you\'re doing!\nThese options may change behavior, or be removed entirely in future updates.</string>
|
<string name="preference_developer_mode_about">Developer mode enables some options that may make the game unstable. Please do not modify them unless you know what you\'re doing!\nThese options may change behavior, or be removed entirely in future updates.</string>
|
||||||
@@ -258,4 +260,9 @@
|
|||||||
<string name="preference_override_exceptions_name">Custom exceptions fix symbols</string>
|
<string name="preference_override_exceptions_name">Custom exceptions fix symbols</string>
|
||||||
<string name="preference_override_exceptions_description">Uses exception_symbols.txt to patch</string>
|
<string name="preference_override_exceptions_description">Uses exception_symbols.txt to patch</string>
|
||||||
<string name="preference_use_index_update_api">Use index release API</string>
|
<string name="preference_use_index_update_api">Use index release API</string>
|
||||||
|
|
||||||
|
<!-- CrashLogsActivity strings -->
|
||||||
|
<string name="title_activity_crash_logs">Crash Dumps</string>
|
||||||
|
<string name="application_crashes_no_dumps">No crash dumps available.</string>
|
||||||
|
<string name="application_crashes_delete_one">Delete</string>
|
||||||
</resources>
|
</resources>
|
||||||
Reference in new issue
Block a user