add developer options menu

This commit is contained in:
qimiko committed 2024-06-20 23:07:09 -07:00
1 parent e6fa0d491d
commit 375f771e8a
9 files changed
+304 -55

No files matched your search

+5 -1
View File
@@ -41,7 +41,11 @@
<activity
android:name=".AltMainActivity"
android:exported="false"
android:label="@string/title_activity_alt_main"
android:theme="@style/Theme.GeodeLauncher" />
<activity
android:name=".preferences.DeveloperSettingsActivity"
android:exported="false"
android:label="@string/title_activity_developer_settings"
android:theme="@style/Theme.GeodeLauncher" />
<activity
android:name=".preferences.ApplicationLogsActivity"
@@ -0,0 +1,169 @@
package com.geode.launcher.preferences
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Info
import androidx.compose.material3.ElevatedCard
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
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.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.geode.launcher.R
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.utils.LabelledText
import com.geode.launcher.utils.PreferenceUtils
class DeveloperSettingsActivity : 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)
CompositionLocalProvider(LocalTheme provides theme) {
GeodeLauncherTheme(theme = theme, blackBackground = backgroundOption) {
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background
) {
DeveloperSettingsScreen(onBackPressedDispatcher)
}
}
}
}
}
}
@Composable
fun releaseChannelToKey(option: Int): String = when (option) {
1 -> stringResource(R.string.preference_release_channel_beta)
2 -> stringResource(R.string.preference_release_channel_nightly)
else -> stringResource(R.string.preference_release_channel_stable)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
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)
)
}
},
title = {
Text(
stringResource(R.string.title_activity_developer_settings),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
},
scrollBehavior = scrollBehavior,
)
}
}
) { innerPadding ->
Column(
Modifier
.padding(innerPadding)
.fillMaxWidth()
.verticalScroll(rememberScrollState()),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
val developerMode by PreferenceUtils.useBooleanPreference(PreferenceUtils.Key.DEVELOPER_MODE)
SettingsCard(
title = stringResource(R.string.preference_developer_mode),
preferenceKey = PreferenceUtils.Key.DEVELOPER_MODE,
)
InlineText(
stringResource(R.string.preference_developer_mode_about)
)
HorizontalDivider()
if (developerMode) {
OptionsGroup(title = stringResource(R.string.preference_category_gameplay)) {
SettingsStringCard(
title = stringResource(R.string.preference_launch_arguments_name),
dialogTitle = stringResource(R.string.preference_launch_arguments_set_title),
preferenceKey = PreferenceUtils.Key.LAUNCH_ARGUMENTS,
filterInput = { it.filter { c ->
// if only there was a better way to define this!
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}<>[]?:;'\"~`-_+=\\| ".contains(c)
}}
)
}
OptionsGroup(title = stringResource(R.string.preference_category_updater)) {
SettingsSelectCard(
title = stringResource(R.string.preference_release_channel_tag_name),
dialogTitle = stringResource(R.string.preference_release_channel_select),
maxVal = 2,
preferenceKey = PreferenceUtils.Key.RELEASE_CHANNEL_TAG,
toLabel = { releaseChannelToKey(it) }
)
ElevatedCard(modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)) {
LabelledText(
label = stringResource(R.string.preference_developer_update_notice),
icon = {
Icon(Icons.Filled.Info, null)
},
modifier = Modifier.padding(8.dp)
)
}
}
OptionsGroup(title = stringResource(R.string.preference_category_testing)) {
SettingsCard(
title = stringResource(R.string.preference_enable_redesign),
preferenceKey = PreferenceUtils.Key.ENABLE_REDESIGN,
)
}
}
}
}
}
@@ -36,6 +36,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
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.semantics.Role
import androidx.compose.ui.text.style.TextOverflow
@@ -205,13 +206,6 @@ fun displayOptionToKey(option: Int): String {
}
}
@Composable
fun releaseChannelToKey(option: Int): String = when (option) {
1 -> stringResource(R.string.preference_release_channel_beta)
2 -> stringResource(R.string.preference_release_channel_nightly)
else -> stringResource(R.string.preference_release_channel_stable)
}
fun updateTheme(context: Context, theme: Int) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
val uiModeManager = context.getSystemService(Context.UI_MODE_SERVICE) as UiModeManager
@@ -238,6 +232,11 @@ fun onOpenLogs(context: Context) {
context.startActivity(launchIntent)
}
fun onOpenDeveloperOptions(context: Context) {
val launchIntent = Intent(context, DeveloperSettingsActivity::class.java)
context.startActivity(launchIntent)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(
@@ -316,6 +315,13 @@ fun SettingsScreen(
title = stringResource(R.string.preference_black_background_name),
preferenceKey = PreferenceUtils.Key.BLACK_BACKGROUND
)
OptionsButton(
title = stringResource(R.string.preferences_open_file_manager),
onClick = { onOpenFileManager(context) }
)
}
OptionsGroup(stringResource(R.string.preference_category_gameplay)) {
SettingsCard(
title = context.getString(R.string.preference_load_automatically_name),
description = context.getString(R.string.preference_load_automatically_description),
@@ -331,14 +337,10 @@ fun SettingsScreen(
)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
SettingsCard(
title = context.getString(R.string.preference_force_hrr),
title = stringResource(R.string.preference_force_hrr),
preferenceKey = PreferenceUtils.Key.FORCE_HRR,
)
}
OptionsButton(
title = stringResource(R.string.preferences_open_file_manager),
onClick = { onOpenFileManager(context) }
)
}
OptionsGroup(context.getString(R.string.preference_category_updater)) {
@@ -346,13 +348,6 @@ fun SettingsScreen(
title = context.getString(R.string.preference_update_automatically_name),
preferenceKey = PreferenceUtils.Key.UPDATE_AUTOMATICALLY,
)
SettingsSelectCard(
title = stringResource(R.string.preference_release_channel_tag_name),
dialogTitle = stringResource(R.string.preference_release_channel_select),
maxVal = 2,
preferenceKey = PreferenceUtils.Key.RELEASE_CHANNEL_TAG,
toLabel = { releaseChannelToKey(it) }
)
OptionsCard(
title = {
OptionsTitle(
@@ -380,28 +375,25 @@ fun SettingsScreen(
}
OptionsGroup(stringResource(R.string.preference_category_developer)) {
SettingsStringCard(
title = stringResource(R.string.preference_launch_arguments_name),
dialogTitle = stringResource(R.string.preference_launch_arguments_set_title),
preferenceKey = PreferenceUtils.Key.LAUNCH_ARGUMENTS,
filterInput = { it.filter { c ->
// if only there was a better way to define this!
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}<>[]?:;'\"~`-_+=\\| ".contains(c)
}}
OptionsButton(
title = stringResource(R.string.preferences_view_logs),
icon = {
Icon(painterResource(R.drawable.icon_description), contentDescription = null)
},
onClick = { onOpenLogs(context) }
)
OptionsButton(
title = stringResource(R.string.preference_open_developer_options),
icon = {
Icon(painterResource(R.drawable.icon_data_object), contentDescription = null)
},
onClick = { onOpenDeveloperOptions(context) }
)
OptionsButton(
title = context.getString(R.string.preferences_copy_external_button),
description = LaunchUtils.getBaseDirectory(context).path,
onClick = { onOpenFolder(context) }
)
OptionsButton(
title = stringResource(R.string.preferences_view_logs),
onClick = { onOpenLogs(context) }
)
SettingsCard(
title = context.getString(R.string.preference_enable_redesign),
preferenceKey = PreferenceUtils.Key.ENABLE_REDESIGN,
)
}
Text(
@@ -40,6 +40,7 @@ import androidx.compose.ui.window.Dialog
import com.geode.launcher.R
import com.geode.launcher.ui.theme.GeodeLauncherTheme
import com.geode.launcher.ui.theme.Typography
import com.geode.launcher.utils.LabelledText
import com.geode.launcher.utils.PreferenceUtils
@@ -278,12 +279,13 @@ fun <T> SelectDialog(
}
@Composable
fun OptionsButton(title: String, description: String? = null, onClick: () -> Unit) {
fun OptionsButton(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, onClick: () -> Unit) {
OptionsCard(
title = {
OptionsTitle(
title = title,
description = description
description = description,
icon = icon
)
},
modifier = Modifier
@@ -292,7 +294,7 @@ fun OptionsButton(title: String, description: String? = null, onClick: () -> Uni
}
@Composable
fun SettingsCard(title: String, description: String? = null, preferenceKey: PreferenceUtils.Key) {
fun SettingsCard(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, preferenceKey: PreferenceUtils.Key) {
val context = LocalContext.current
val settingEnabled = remember {
mutableStateOf(getSetting(context, preferenceKey))
@@ -303,7 +305,8 @@ fun SettingsCard(title: String, description: String? = null, preferenceKey: Pref
OptionsTitle(
Modifier.fillMaxWidth(0.75f),
title = title,
description = description
description = description,
icon = icon
)
},
modifier = Modifier.toggleable(
@@ -317,18 +320,23 @@ fun SettingsCard(title: String, description: String? = null, preferenceKey: Pref
}
@Composable
fun OptionsTitle(modifier: Modifier = Modifier, title: String, description: String? = null) {
Column(
modifier,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(title)
if (!description.isNullOrEmpty()) {
Text(
description,
style = Typography.labelMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
fun OptionsTitle(modifier: Modifier = Modifier, title: String, description: String? = null, icon: (@Composable () -> Unit)? = null) {
Row(horizontalArrangement = Arrangement.spacedBy(8.dp)) {
if (icon != null) {
icon()
}
Column(
modifier,
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(title)
if (!description.isNullOrEmpty()) {
Text(
description,
style = Typography.labelMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
}
}
}
}
@@ -348,6 +356,11 @@ fun OptionsCard(modifier: Modifier = Modifier, title: @Composable () -> Unit, co
}
}
@Composable
fun InlineText(label: String, icon: @Composable (() -> Unit)? = null, modifier: Modifier = Modifier) {
LabelledText(label = label, icon = icon, modifier = modifier.padding(horizontal = 16.dp, vertical = 4.dp))
}
@Preview(showBackground = true)
@Composable
fun OptionsCardPreview() {
@@ -182,6 +182,10 @@ class ReleaseManager private constructor(
}
val sharedPreferences = PreferenceUtils.get(applicationContext)
if (!sharedPreferences.getBoolean(PreferenceUtils.Key.DEVELOPER_MODE)) {
return false
}
val originalFileHash = sharedPreferences.getString(PreferenceUtils.Key.CURRENT_RELEASE_MODIFIED)
?: return false
@@ -0,0 +1,46 @@
package com.geode.launcher.utils
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.InlineTextContent
import androidx.compose.foundation.text.appendInlineContent
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.Placeholder
import androidx.compose.ui.text.PlaceholderVerticalAlign
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.unit.sp
@Composable
fun LabelledText(label: String, icon: @Composable (() -> Unit)? = null, modifier: Modifier = Modifier) {
if (icon != null) {
val iconId = "inlineIcon"
val paddingId = "paddingIcon"
val labelText = buildAnnotatedString {
appendInlineContent(iconId, "[x]")
appendInlineContent(paddingId, " ")
append(label)
}
val inlineContent = mapOf(
Pair(iconId, InlineTextContent(
Placeholder(
width = 24.sp,
height = 24.sp,
placeholderVerticalAlign = PlaceholderVerticalAlign.TextCenter
)
) {
icon()
}),
Pair(paddingId, InlineTextContent(Placeholder(width = 6.sp, height = 24.sp, placeholderVerticalAlign = PlaceholderVerticalAlign.Center)) {
Spacer(modifier = Modifier.fillMaxWidth())
})
)
Text(text = labelText, inlineContent = inlineContent, modifier = modifier)
} else {
Text(label, modifier = modifier)
}
}
@@ -138,12 +138,14 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
DISPLAY_MODE,
FORCE_HRR,
ENABLE_REDESIGN,
RELEASE_CHANNEL_TAG
RELEASE_CHANNEL_TAG,
DEVELOPER_MODE
}
private fun defaultValueForBooleanKey(key: Key): Boolean {
return when (key) {
Key.UPDATE_AUTOMATICALLY, Key.FORCE_HRR -> true
Key.DEVELOPER_MODE -> BuildConfig.DEBUG
else -> false
}
}
@@ -172,6 +174,7 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
Key.FORCE_HRR -> "PreferenceForceHighRefreshRate"
Key.ENABLE_REDESIGN -> "PreferenceEnableRedesign"
Key.RELEASE_CHANNEL_TAG -> "PreferenceReleaseChannelTag"
Key.DEVELOPER_MODE -> "PreferenceDeveloperMode"
}
}
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M560,800L560,720L680,720Q697,720 708.5,708.5Q720,697 720,680L720,600Q720,562 742,531Q764,500 800,487L800,473Q764,460 742,429Q720,398 720,360L720,280Q720,263 708.5,251.5Q697,240 680,240L560,240L560,160L680,160Q730,160 765,195Q800,230 800,280L800,360Q800,377 811.5,388.5Q823,400 840,400L880,400L880,560L840,560Q823,560 811.5,571.5Q800,583 800,600L800,680Q800,730 765,765Q730,800 680,800L560,800ZM280,800Q230,800 195,765Q160,730 160,680L160,600Q160,583 148.5,571.5Q137,560 120,560L80,560L80,400L120,400Q137,400 148.5,388.5Q160,377 160,360L160,280Q160,230 195,195Q230,160 280,160L400,160L400,240L280,240Q263,240 251.5,251.5Q240,263 240,280L240,360Q240,398 218,429Q196,460 160,473L160,487Q196,500 218,531Q240,562 240,600L240,680Q240,697 251.5,708.5Q263,720 280,720L400,720L400,800L280,800Z"/>
</vector>
+10 -2
View File
@@ -135,6 +135,8 @@
<string name="preference_update_automatically_name">Enable automatic updates</string>
<string name="preference_release_channel_name">Use nightly release channel</string>
<string name="preference_category_gameplay">Gameplay</string>
<string name="preference_release_channel_tag_name">Release channel</string>
<string name="preference_release_channel_select">Select release channel</string>
<string name="preference_release_channel_stable">Stable</string>
@@ -151,11 +153,13 @@
<string name="preference_check_for_updates_no_internet">No internet found.</string>
<string name="preference_check_for_updates_already_updating">An update is already in progress!</string>
<string name="preference_category_developer">Developer</string>
<string name="preference_category_developer">Advanced</string>
<string name="preference_launch_arguments_name">Launch arguments</string>
<string name="preference_launch_arguments_set_title">Set launch arguments</string>
<string name="preference_text_clear">Clear</string>
<string name="preference_open_developer_options">Open developer options</string>
<!-- ApplicationLogsActivity strings -->
<string name="title_activity_application_logs">Application Logs</string>
<string name="application_logs_no_logs">No logs available.</string>
@@ -170,5 +174,9 @@
<string name="application_logs_default_filename" translatable="false">launcher_logcat.txt</string>
<string name="application_logs_crash_only">Show Crash Buffer</string>
<string name="application_logs_crash_only_enabled_alt">Enabled</string>
<string name="title_activity_alt_main">AltMainActivity</string>
<string name="title_activity_developer_settings">Developer Settings</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_update_notice">Enabling developer mode automatically disallows the updater from overwriting custom loader builds.</string>
</resources>