forked from geode-sdk/android-launcher
reorganize settings utils into package
This commit is contained in:
9 files changed
+573
-487
No files matched your search
@@ -55,6 +55,8 @@ import com.geode.launcher.R
|
|||||||
import com.geode.launcher.log.LogLine
|
import com.geode.launcher.log.LogLine
|
||||||
import com.geode.launcher.log.LogPriority
|
import com.geode.launcher.log.LogPriority
|
||||||
import com.geode.launcher.log.LogViewModel
|
import com.geode.launcher.log.LogViewModel
|
||||||
|
import com.geode.launcher.preferences.components.SelectDialog
|
||||||
|
import com.geode.launcher.preferences.components.SelectOption
|
||||||
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
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import com.geode.launcher.R
|
|||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
import com.geode.launcher.log.CrashDump
|
import com.geode.launcher.log.CrashDump
|
||||||
import com.geode.launcher.log.CrashViewModel
|
import com.geode.launcher.log.CrashViewModel
|
||||||
|
import com.geode.launcher.preferences.components.DirectoryListingScreen
|
||||||
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
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
|
import com.geode.launcher.preferences.components.OptionsButton
|
||||||
|
import com.geode.launcher.preferences.components.OptionsGroup
|
||||||
|
import com.geode.launcher.preferences.components.ProfileCreateCard
|
||||||
|
import com.geode.launcher.preferences.components.ProfileSelectCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsSelectCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsStringCard
|
||||||
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
|
||||||
@@ -151,10 +158,14 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
title = stringResource(R.string.preference_launch_arguments_name),
|
title = stringResource(R.string.preference_launch_arguments_name),
|
||||||
dialogTitle = stringResource(R.string.preference_launch_arguments_set_title),
|
dialogTitle = stringResource(R.string.preference_launch_arguments_set_title),
|
||||||
preferenceKey = PreferenceUtils.Key.LAUNCH_ARGUMENTS,
|
preferenceKey = PreferenceUtils.Key.LAUNCH_ARGUMENTS,
|
||||||
filterInput = { it.filter { c ->
|
filterInput = {
|
||||||
|
it.filter { c ->
|
||||||
// if only there was a better way to define this!
|
// if only there was a better way to define this!
|
||||||
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}<>[]?:;'\"~`-_+=\\| ".contains(c)
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*(){}<>[]?:;'\"~`-_+=\\| ".contains(
|
||||||
}}
|
c
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -238,9 +249,21 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
|
|||||||
preferenceKey = PreferenceUtils.Key.WAIT_PERIOD,
|
preferenceKey = PreferenceUtils.Key.WAIT_PERIOD,
|
||||||
options = linkedMapOf(
|
options = linkedMapOf(
|
||||||
0 to stringResource(R.string.preference_launch_delay_instant),
|
0 to stringResource(R.string.preference_launch_delay_instant),
|
||||||
1 to pluralStringResource(R.plurals.preference_launch_delay_value, 1, 1),
|
1 to pluralStringResource(
|
||||||
3 to pluralStringResource(R.plurals.preference_launch_delay_value, 3, 3),
|
R.plurals.preference_launch_delay_value,
|
||||||
5 to pluralStringResource(R.plurals.preference_launch_delay_value, 5, 5),
|
1,
|
||||||
|
1
|
||||||
|
),
|
||||||
|
3 to pluralStringResource(
|
||||||
|
R.plurals.preference_launch_delay_value,
|
||||||
|
3,
|
||||||
|
3
|
||||||
|
),
|
||||||
|
5 to pluralStringResource(
|
||||||
|
R.plurals.preference_launch_delay_value,
|
||||||
|
5,
|
||||||
|
5
|
||||||
|
),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -22,13 +22,6 @@ 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.items
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
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.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@@ -36,31 +29,25 @@ import androidx.compose.material3.Surface
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
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.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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
import com.geode.launcher.log.GeodeLog
|
import com.geode.launcher.log.GeodeLog
|
||||||
import com.geode.launcher.log.GeodeLogsViewModel
|
import com.geode.launcher.log.GeodeLogsViewModel
|
||||||
|
import com.geode.launcher.preferences.components.DirectoryListingScreen
|
||||||
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
|
||||||
|
|||||||
@@ -73,6 +73,18 @@ import com.geode.launcher.BuildConfig
|
|||||||
import com.geode.launcher.MainActivity
|
import com.geode.launcher.MainActivity
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.UserDirectoryProvider
|
import com.geode.launcher.UserDirectoryProvider
|
||||||
|
import com.geode.launcher.preferences.components.BackupButton
|
||||||
|
import com.geode.launcher.preferences.components.OptionsButton
|
||||||
|
import com.geode.launcher.preferences.components.OptionsCard
|
||||||
|
import com.geode.launcher.preferences.components.OptionsGroup
|
||||||
|
import com.geode.launcher.preferences.components.OptionsLabel
|
||||||
|
import com.geode.launcher.preferences.components.OptionsTitle
|
||||||
|
import com.geode.launcher.preferences.components.SafeModeDialog
|
||||||
|
import com.geode.launcher.preferences.components.SettingsCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsFPSCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsRangeCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsSelectCard
|
||||||
|
import com.geode.launcher.preferences.components.SettingsStringSelectCard
|
||||||
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
|
||||||
@@ -531,7 +543,10 @@ fun UpdateSettingsGroup(releaseViewModel: ReleaseViewModel, snackbarHostState: S
|
|||||||
contentDescription = null
|
contentDescription = null
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
description = stringResource(R.string.preference_check_for_updates_description, checkAgoString)
|
description = stringResource(
|
||||||
|
R.string.preference_check_for_updates_description,
|
||||||
|
checkAgoString
|
||||||
|
)
|
||||||
.takeIf { lastUpdateCheck != 0L }
|
.takeIf { lastUpdateCheck != 0L }
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -596,14 +611,21 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
|||||||
|
|
||||||
OptionsButton(
|
OptionsButton(
|
||||||
stringResource(R.string.preference_launcher_version_name),
|
stringResource(R.string.preference_launcher_version_name),
|
||||||
stringResource(R.string.preference_launcher_version_description, BuildConfig.VERSION_NAME),
|
stringResource(
|
||||||
|
R.string.preference_launcher_version_description,
|
||||||
|
BuildConfig.VERSION_NAME
|
||||||
|
),
|
||||||
displayInline = true
|
displayInline = true
|
||||||
) {
|
) {
|
||||||
launcherButtonTapped += 1
|
launcherButtonTapped += 1
|
||||||
|
|
||||||
if (launcherButtonTapped >= 7) {
|
if (launcherButtonTapped >= 7) {
|
||||||
if (developerModeEnabled) {
|
if (developerModeEnabled) {
|
||||||
Toast.makeText(context, R.string.preference_enable_developer_again, Toast.LENGTH_SHORT)
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
R.string.preference_enable_developer_again,
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
showDeveloperDialog = true
|
showDeveloperDialog = true
|
||||||
@@ -618,7 +640,8 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
|||||||
showDeveloperDialog = false
|
showDeveloperDialog = false
|
||||||
},
|
},
|
||||||
onEnable = {
|
onEnable = {
|
||||||
PreferenceUtils.get(context).setBoolean(PreferenceUtils.Key.DEVELOPER_MODE, true)
|
PreferenceUtils.get(context)
|
||||||
|
.setBoolean(PreferenceUtils.Key.DEVELOPER_MODE, true)
|
||||||
showDeveloperDialog = false
|
showDeveloperDialog = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
@@ -626,7 +649,10 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
|||||||
|
|
||||||
OptionsLabel(
|
OptionsLabel(
|
||||||
stringResource(R.string.preference_loader_version_name),
|
stringResource(R.string.preference_loader_version_name),
|
||||||
stringResource(R.string.preference_loader_version_description, currentRelease ?: "unknown"),
|
stringResource(
|
||||||
|
R.string.preference_loader_version_description,
|
||||||
|
currentRelease ?: "unknown"
|
||||||
|
),
|
||||||
displayInline = true
|
displayInline = true
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -640,22 +666,32 @@ fun AboutSettingsGroup(developerModeEnabled: Boolean) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val gameSource = remember {
|
val gameSource = remember {
|
||||||
resources.getString(when (GamePackageUtils.identifyGameSource(context.packageManager)) {
|
resources.getString(
|
||||||
|
when (GamePackageUtils.identifyGameSource(context.packageManager)) {
|
||||||
GamePackageUtils.GameSource.GOOGLE -> R.string.preference_game_source_google
|
GamePackageUtils.GameSource.GOOGLE -> R.string.preference_game_source_google
|
||||||
GamePackageUtils.GameSource.AMAZON -> R.string.preference_game_source_amazon
|
GamePackageUtils.GameSource.AMAZON -> R.string.preference_game_source_amazon
|
||||||
else -> R.string.preference_game_source_unknown
|
else -> R.string.preference_game_source_unknown
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
OptionsLabel(
|
OptionsLabel(
|
||||||
stringResource(R.string.preference_game_version_name),
|
stringResource(R.string.preference_game_version_name),
|
||||||
stringResource(R.string.preference_game_version_description, gameVersion, gameSource),
|
stringResource(
|
||||||
|
R.string.preference_game_version_description,
|
||||||
|
gameVersion,
|
||||||
|
gameSource
|
||||||
|
),
|
||||||
displayInline = true
|
displayInline = true
|
||||||
)
|
)
|
||||||
|
|
||||||
OptionsLabel(
|
OptionsLabel(
|
||||||
stringResource(R.string.preference_architecture_name),
|
stringResource(R.string.preference_architecture_name),
|
||||||
stringResource(R.string.preference_architecture_description, Build.MODEL, LaunchUtils.applicationArchitecture),
|
stringResource(
|
||||||
|
R.string.preference_architecture_description,
|
||||||
|
Build.MODEL,
|
||||||
|
LaunchUtils.applicationArchitecture
|
||||||
|
),
|
||||||
displayInline = true
|
displayInline = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.geode.launcher.preferences
|
package com.geode.launcher.preferences.components
|
||||||
|
|
||||||
import androidx.activity.OnBackPressedDispatcher
|
import androidx.activity.OnBackPressedDispatcher
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.geode.launcher.preferences
|
package com.geode.launcher.preferences.components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
+3
-456
@@ -1,4 +1,4 @@
|
|||||||
package com.geode.launcher.preferences
|
package com.geode.launcher.preferences.components
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -8,41 +8,28 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.ColumnScope
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.defaultMinSize
|
import androidx.compose.foundation.layout.defaultMinSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.selection.toggleable
|
import androidx.compose.foundation.selection.toggleable
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.AlertDialog
|
import androidx.compose.material3.AlertDialog
|
||||||
import androidx.compose.material3.Card
|
|
||||||
import androidx.compose.material3.CardDefaults
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.OutlinedTextField
|
|
||||||
import androidx.compose.material3.RadioButton
|
|
||||||
import androidx.compose.material3.Slider
|
|
||||||
import androidx.compose.material3.Switch
|
import androidx.compose.material3.Switch
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
|
||||||
import androidx.compose.runtime.mutableStateListOf
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
|
||||||
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
|
||||||
@@ -51,21 +38,17 @@ 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.semantics.Role
|
import androidx.compose.ui.semantics.Role
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import com.geode.launcher.R
|
import com.geode.launcher.R
|
||||||
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
import com.geode.launcher.ui.theme.GeodeLauncherTheme
|
||||||
import com.geode.launcher.ui.theme.Typography
|
import com.geode.launcher.ui.theme.Typography
|
||||||
import com.geode.launcher.utils.LabelledText
|
import com.geode.launcher.utils.LabelledText
|
||||||
import com.geode.launcher.utils.PreferenceUtils
|
import com.geode.launcher.utils.PreferenceUtils
|
||||||
import com.geode.launcher.utils.Profile
|
|
||||||
import com.geode.launcher.utils.ProfileManager
|
import com.geode.launcher.utils.ProfileManager
|
||||||
|
import kotlin.collections.get
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
@@ -237,244 +220,6 @@ fun SettingsStringCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun StringDialog(
|
|
||||||
title: String,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onSelect: (String) -> Unit,
|
|
||||||
initialValue: String,
|
|
||||||
filterInput: ((String) -> String)? = null
|
|
||||||
) {
|
|
||||||
var enteredValue by remember { mutableStateOf(initialValue) }
|
|
||||||
|
|
||||||
BaseDialog(
|
|
||||||
onDismissRequest = { onDismissRequest() },
|
|
||||||
title = title,
|
|
||||||
content = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 12.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = enteredValue,
|
|
||||||
onValueChange = {
|
|
||||||
enteredValue = filterInput?.invoke(it) ?: it
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Ascii,
|
|
||||||
autoCorrectEnabled = false
|
|
||||||
),
|
|
||||||
trailingIcon = {
|
|
||||||
if (enteredValue.isNotEmpty()) {
|
|
||||||
IconButton(onClick = { enteredValue = "" }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_close_small),
|
|
||||||
contentDescription = stringResource(R.string.preference_text_clear)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { onSelect(enteredValue) }) {
|
|
||||||
Text(stringResource(R.string.message_box_accept))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(stringResource(R.string.message_box_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun RangeDialog(
|
|
||||||
title: String,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onSelect: (Int) -> Unit,
|
|
||||||
labelSuffix: String,
|
|
||||||
initialValue: Int,
|
|
||||||
range: IntRange,
|
|
||||||
scale: Int,
|
|
||||||
step: Int,
|
|
||||||
children: @Composable () -> Unit
|
|
||||||
) {
|
|
||||||
var enteredValue by remember {
|
|
||||||
mutableFloatStateOf(initialValue / scale.toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseDialog(
|
|
||||||
onDismissRequest = { onDismissRequest() },
|
|
||||||
title = title,
|
|
||||||
content = {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
|
|
||||||
) {
|
|
||||||
val precision = log10(scale.toFloat()).roundToInt()
|
|
||||||
|
|
||||||
Slider(
|
|
||||||
value = enteredValue,
|
|
||||||
onValueChange = { enteredValue = it },
|
|
||||||
valueRange = (range.first.toFloat()/scale)..(range.last.toFloat()/scale),
|
|
||||||
steps = ((range.last - range.first) - 1) / step,
|
|
||||||
modifier = Modifier.weight(1.0f)
|
|
||||||
)
|
|
||||||
|
|
||||||
Text("%.${precision}f$labelSuffix".format(enteredValue))
|
|
||||||
}
|
|
||||||
|
|
||||||
children()
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { onSelect((enteredValue * scale).roundToInt()) }) {
|
|
||||||
Text(stringResource(R.string.message_box_accept))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(stringResource(R.string.message_box_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun FrameRateDialog(
|
|
||||||
title: String,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onSelect: (Int) -> Unit,
|
|
||||||
initialValue: Int,
|
|
||||||
maxFrameRate: Int,
|
|
||||||
) {
|
|
||||||
var enteredValue by remember {
|
|
||||||
mutableStateOf(
|
|
||||||
if (initialValue == 0) maxFrameRate.toString()
|
|
||||||
else initialValue.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
val minFrameRate = 5
|
|
||||||
val currentValue = enteredValue.toIntOrNull()
|
|
||||||
val maximumReached = currentValue != null && currentValue > maxFrameRate
|
|
||||||
val minimumReached = currentValue != null && currentValue < minFrameRate
|
|
||||||
|
|
||||||
BaseDialog(
|
|
||||||
onDismissRequest = { onDismissRequest() },
|
|
||||||
title = title,
|
|
||||||
content = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 12.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
) {
|
|
||||||
IconButton(onClick = {
|
|
||||||
val prevValue = max((currentValue ?: 0) - 5, minFrameRate)
|
|
||||||
enteredValue = prevValue.toString()
|
|
||||||
}, enabled = currentValue == null || currentValue > minFrameRate) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_remove),
|
|
||||||
contentDescription = stringResource(R.string.preference_limit_framerate_subtract)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
OutlinedTextField(
|
|
||||||
value = enteredValue,
|
|
||||||
onValueChange = {
|
|
||||||
enteredValue = it
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Number,
|
|
||||||
),
|
|
||||||
suffix = {
|
|
||||||
Text(stringResource(R.string.preference_limit_framerate_suffix))
|
|
||||||
},
|
|
||||||
label = {
|
|
||||||
Text(stringResource(R.string.preference_limit_framerate_label))
|
|
||||||
},
|
|
||||||
isError = minimumReached || maximumReached,
|
|
||||||
modifier = Modifier.weight(1.0f),
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
IconButton(onClick = {
|
|
||||||
val nextValue = min((currentValue ?: 0) + 5, maxFrameRate)
|
|
||||||
enteredValue = nextValue.toString()
|
|
||||||
}, enabled = currentValue == null || currentValue < maxFrameRate) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_add),
|
|
||||||
contentDescription = stringResource(R.string.preference_limit_framerate_add)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
IconButton(onClick = { onSelect(0) }) {
|
|
||||||
Icon(
|
|
||||||
painterResource(R.drawable.icon_delete),
|
|
||||||
contentDescription = stringResource(R.string.preference_limit_framerate_reset)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (minimumReached) {
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.preference_limit_framerate_error_min, minFrameRate),
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
)
|
|
||||||
} else if (maximumReached) {
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
stringResource(
|
|
||||||
R.string.preference_limit_framerate_error_max,
|
|
||||||
maxFrameRate
|
|
||||||
),
|
|
||||||
color = MaterialTheme.colorScheme.error,
|
|
||||||
style = MaterialTheme.typography.labelLarge,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
// automatically disable if already max
|
|
||||||
if (currentValue == maxFrameRate) {
|
|
||||||
onSelect(0)
|
|
||||||
} else {
|
|
||||||
onSelect(currentValue ?: 0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
enabled = !minimumReached && !maximumReached && currentValue != null
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.message_box_accept))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(stringResource(R.string.message_box_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SettingsFPSCard(
|
fun SettingsFPSCard(
|
||||||
title: String,
|
title: String,
|
||||||
@@ -570,120 +315,6 @@ fun SettingsRangeCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal val LocalSelectValue = compositionLocalOf<Any> { 0 }
|
|
||||||
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> SelectOption(name: String, value: T, enabled: Boolean = true, leadingContent: @Composable (Boolean) -> Unit = {}) {
|
|
||||||
val currentValue = LocalSelectValue.current
|
|
||||||
val setValue = LocalSelectSetValue.current
|
|
||||||
|
|
||||||
val isSelected = currentValue.equals(value)
|
|
||||||
|
|
||||||
// do not give the row or column padding!! it messes up the selection effect
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable(
|
|
||||||
onClick = { setValue(value as Any) },
|
|
||||||
role = Role.RadioButton,
|
|
||||||
enabled = enabled
|
|
||||||
)
|
|
||||||
.padding(horizontal = 12.dp)
|
|
||||||
) {
|
|
||||||
Row(modifier = Modifier.weight(1.0f, true), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
RadioButton(
|
|
||||||
selected = isSelected,
|
|
||||||
onClick = { setValue(value as Any) },
|
|
||||||
enabled = enabled
|
|
||||||
)
|
|
||||||
Text(name, style = Typography.bodyMedium)
|
|
||||||
}
|
|
||||||
|
|
||||||
leadingContent(isSelected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ProfileCreateDialog(onDismissRequest: () -> Unit) {
|
|
||||||
var enteredValue by remember { mutableStateOf("") }
|
|
||||||
|
|
||||||
val filename = enteredValue.take(16)
|
|
||||||
.lowercase()
|
|
||||||
.map {
|
|
||||||
if ("qwertyuiopasdfghjklzxcvbnm1234567890-_.".contains(it))
|
|
||||||
it else '_'
|
|
||||||
}
|
|
||||||
.joinToString("")
|
|
||||||
|
|
||||||
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
val minimumReached = enteredValue.isEmpty() || filename.isEmpty()
|
|
||||||
|
|
||||||
val profileManager = ProfileManager.get(context)
|
|
||||||
val currentProfiles = remember {
|
|
||||||
profileManager.getProfiles().map { it.path }.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
val isDuplicate = currentProfiles.contains(filename)
|
|
||||||
|
|
||||||
BaseDialog(
|
|
||||||
onDismissRequest = { onDismissRequest() },
|
|
||||||
title = stringResource(R.string.preference_profiles_create),
|
|
||||||
content = {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 12.dp)
|
|
||||||
) {
|
|
||||||
OutlinedTextField(
|
|
||||||
value = enteredValue,
|
|
||||||
onValueChange = {
|
|
||||||
enteredValue = it
|
|
||||||
},
|
|
||||||
singleLine = true,
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Text,
|
|
||||||
),
|
|
||||||
label = {
|
|
||||||
Text(stringResource(R.string.preference_profiles_create_name))
|
|
||||||
},
|
|
||||||
isError = isDuplicate,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isDuplicate) {
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
Text(stringResource(R.string.preference_profiles_create_duplicate))
|
|
||||||
} else if (!minimumReached) {
|
|
||||||
Spacer(Modifier.size(8.dp))
|
|
||||||
|
|
||||||
Text(stringResource(R.string.preference_profiles_create_info, filename))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(
|
|
||||||
onClick = {
|
|
||||||
ProfileManager.get(context).storeProfile(Profile(filename, enteredValue))
|
|
||||||
onDismissRequest()
|
|
||||||
},
|
|
||||||
enabled = !minimumReached && !isDuplicate
|
|
||||||
) {
|
|
||||||
Text(stringResource(R.string.preference_profiles_create_action))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(stringResource(R.string.message_box_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileCreateCard() {
|
fun ProfileCreateCard() {
|
||||||
var showDialog by remember { mutableStateOf(false) }
|
var showDialog by remember { mutableStateOf(false) }
|
||||||
@@ -713,7 +344,7 @@ fun ProfileSelectCard() {
|
|||||||
profileManager.getCurrentProfile()
|
profileManager.getCurrentProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
var currentProfileName = remember {
|
val currentProfileName = remember {
|
||||||
val savedProfile = currentProfileId
|
val savedProfile = currentProfileId
|
||||||
if (savedProfile == null) {
|
if (savedProfile == null) {
|
||||||
"Default"
|
"Default"
|
||||||
@@ -803,90 +434,6 @@ fun ProfileSelectCard() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BaseDialog(
|
|
||||||
title: String,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
dismissButton: @Composable () -> Unit,
|
|
||||||
confirmButton: @Composable () -> Unit,
|
|
||||||
content: @Composable (ColumnScope.() -> Unit),
|
|
||||||
) {
|
|
||||||
Dialog(onDismissRequest = onDismissRequest) {
|
|
||||||
Card(
|
|
||||||
shape = RoundedCornerShape(16.dp),
|
|
||||||
colors = CardDefaults.cardColors(
|
|
||||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// styling a dialog is actually a little hard if you're doing what i'm doing
|
|
||||||
// maybe there's a better way to make these padding values...
|
|
||||||
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
|
||||||
Text(
|
|
||||||
title,
|
|
||||||
style = Typography.titleLarge,
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
start = 28.dp,
|
|
||||||
top = 24.dp,
|
|
||||||
bottom = 12.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
content()
|
|
||||||
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.End,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
bottom = 16.dp,
|
|
||||||
end = 16.dp,
|
|
||||||
top = 4.dp
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
dismissButton()
|
|
||||||
confirmButton()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun <T> SelectDialog(
|
|
||||||
title: String,
|
|
||||||
onDismissRequest: () -> Unit,
|
|
||||||
onSelect: (T) -> Unit,
|
|
||||||
initialValue: T,
|
|
||||||
options: @Composable () -> Unit,
|
|
||||||
) {
|
|
||||||
val (selectedValue, setSelectedValue) = remember { mutableStateOf(initialValue) }
|
|
||||||
|
|
||||||
BaseDialog(
|
|
||||||
title = title,
|
|
||||||
onDismissRequest = onDismissRequest,
|
|
||||||
dismissButton = {
|
|
||||||
TextButton(onClick = onDismissRequest) {
|
|
||||||
Text(stringResource(R.string.message_box_cancel))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
confirmButton = {
|
|
||||||
TextButton(onClick = { onSelect(selectedValue) }) {
|
|
||||||
Text(stringResource(R.string.message_box_accept))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalSelectValue provides (selectedValue as Any),
|
|
||||||
LocalSelectSetValue provides ({
|
|
||||||
@Suppress("UNCHECKED_CAST")
|
|
||||||
setSelectedValue(it as T)
|
|
||||||
})
|
|
||||||
) {
|
|
||||||
options()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun OptionsButton(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, displayInline: Boolean = false, onClick: () -> Unit) {
|
fun OptionsButton(title: String, description: String? = null, icon: (@Composable () -> Unit)? = null, displayInline: Boolean = false, onClick: () -> Unit) {
|
||||||
OptionsCard(
|
OptionsCard(
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
package com.geode.launcher.preferences.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.ColumnScope
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.CardDefaults
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.material3.RadioButton
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
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.input.KeyboardType
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import com.geode.launcher.R
|
||||||
|
import com.geode.launcher.ui.theme.Typography
|
||||||
|
import com.geode.launcher.utils.Profile
|
||||||
|
import com.geode.launcher.utils.ProfileManager
|
||||||
|
import kotlin.math.log10
|
||||||
|
import kotlin.math.max
|
||||||
|
import kotlin.math.min
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StringDialog(
|
||||||
|
title: String,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSelect: (String) -> Unit,
|
||||||
|
initialValue: String,
|
||||||
|
filterInput: ((String) -> String)? = null
|
||||||
|
) {
|
||||||
|
var enteredValue by remember { mutableStateOf(initialValue) }
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
onDismissRequest = { onDismissRequest() },
|
||||||
|
title = title,
|
||||||
|
content = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = enteredValue,
|
||||||
|
onValueChange = {
|
||||||
|
enteredValue = filterInput?.invoke(it) ?: it
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Ascii,
|
||||||
|
autoCorrectEnabled = false
|
||||||
|
),
|
||||||
|
trailingIcon = {
|
||||||
|
if (enteredValue.isNotEmpty()) {
|
||||||
|
IconButton(onClick = { enteredValue = "" }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_close_small),
|
||||||
|
contentDescription = stringResource(R.string.preference_text_clear)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onSelect(enteredValue) }) {
|
||||||
|
Text(stringResource(R.string.message_box_accept))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.message_box_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun RangeDialog(
|
||||||
|
title: String,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
labelSuffix: String,
|
||||||
|
initialValue: Int,
|
||||||
|
range: IntRange,
|
||||||
|
scale: Int,
|
||||||
|
step: Int,
|
||||||
|
children: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
var enteredValue by remember {
|
||||||
|
mutableFloatStateOf(initialValue / scale.toFloat())
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
onDismissRequest = { onDismissRequest() },
|
||||||
|
title = title,
|
||||||
|
content = {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||||
|
) {
|
||||||
|
val precision = log10(scale.toFloat()).roundToInt()
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = enteredValue,
|
||||||
|
onValueChange = { enteredValue = it },
|
||||||
|
valueRange = (range.first.toFloat()/scale)..(range.last.toFloat()/scale),
|
||||||
|
steps = ((range.last - range.first) - 1) / step,
|
||||||
|
modifier = Modifier.weight(1.0f)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text("%.${precision}f$labelSuffix".format(enteredValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
children()
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onSelect((enteredValue * scale).roundToInt()) }) {
|
||||||
|
Text(stringResource(R.string.message_box_accept))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.message_box_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun FrameRateDialog(
|
||||||
|
title: String,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSelect: (Int) -> Unit,
|
||||||
|
initialValue: Int,
|
||||||
|
maxFrameRate: Int,
|
||||||
|
) {
|
||||||
|
var enteredValue by remember {
|
||||||
|
mutableStateOf(
|
||||||
|
if (initialValue == 0) maxFrameRate.toString()
|
||||||
|
else initialValue.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
val minFrameRate = 5
|
||||||
|
val currentValue = enteredValue.toIntOrNull()
|
||||||
|
val maximumReached = currentValue != null && currentValue > maxFrameRate
|
||||||
|
val minimumReached = currentValue != null && currentValue < minFrameRate
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
onDismissRequest = { onDismissRequest() },
|
||||||
|
title = title,
|
||||||
|
content = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
|
IconButton(onClick = {
|
||||||
|
val prevValue = max((currentValue ?: 0) - 5, minFrameRate)
|
||||||
|
enteredValue = prevValue.toString()
|
||||||
|
}, enabled = currentValue == null || currentValue > minFrameRate) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_remove),
|
||||||
|
contentDescription = stringResource(R.string.preference_limit_framerate_subtract)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
OutlinedTextField(
|
||||||
|
value = enteredValue,
|
||||||
|
onValueChange = {
|
||||||
|
enteredValue = it
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Number,
|
||||||
|
),
|
||||||
|
suffix = {
|
||||||
|
Text(stringResource(R.string.preference_limit_framerate_suffix))
|
||||||
|
},
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.preference_limit_framerate_label))
|
||||||
|
},
|
||||||
|
isError = minimumReached || maximumReached,
|
||||||
|
modifier = Modifier.weight(1.0f),
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
IconButton(onClick = {
|
||||||
|
val nextValue = min((currentValue ?: 0) + 5, maxFrameRate)
|
||||||
|
enteredValue = nextValue.toString()
|
||||||
|
}, enabled = currentValue == null || currentValue < maxFrameRate) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_add),
|
||||||
|
contentDescription = stringResource(R.string.preference_limit_framerate_add)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
IconButton(onClick = { onSelect(0) }) {
|
||||||
|
Icon(
|
||||||
|
painterResource(R.drawable.icon_delete),
|
||||||
|
contentDescription = stringResource(R.string.preference_limit_framerate_reset)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (minimumReached) {
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.preference_limit_framerate_error_min, minFrameRate),
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
} else if (maximumReached) {
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
stringResource(
|
||||||
|
R.string.preference_limit_framerate_error_max,
|
||||||
|
maxFrameRate
|
||||||
|
),
|
||||||
|
color = MaterialTheme.colorScheme.error,
|
||||||
|
style = MaterialTheme.typography.labelLarge,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
// automatically disable if already max
|
||||||
|
if (currentValue == maxFrameRate) {
|
||||||
|
onSelect(0)
|
||||||
|
} else {
|
||||||
|
onSelect(currentValue ?: 0)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !minimumReached && !maximumReached && currentValue != null
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.message_box_accept))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.message_box_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ProfileCreateDialog(onDismissRequest: () -> Unit) {
|
||||||
|
var enteredValue by remember { mutableStateOf("") }
|
||||||
|
|
||||||
|
val filename = enteredValue.take(16)
|
||||||
|
.lowercase()
|
||||||
|
.map {
|
||||||
|
if ("qwertyuiopasdfghjklzxcvbnm1234567890-_.".contains(it))
|
||||||
|
it else '_'
|
||||||
|
}
|
||||||
|
.joinToString("")
|
||||||
|
|
||||||
|
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
val minimumReached = enteredValue.isEmpty() || filename.isEmpty()
|
||||||
|
|
||||||
|
val profileManager = ProfileManager.get(context)
|
||||||
|
val currentProfiles = remember {
|
||||||
|
profileManager.getProfiles().map { it.path }.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
val isDuplicate = currentProfiles.contains(filename)
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
onDismissRequest = { onDismissRequest() },
|
||||||
|
title = stringResource(R.string.preference_profiles_create),
|
||||||
|
content = {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
value = enteredValue,
|
||||||
|
onValueChange = {
|
||||||
|
enteredValue = it
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Text,
|
||||||
|
),
|
||||||
|
label = {
|
||||||
|
Text(stringResource(R.string.preference_profiles_create_name))
|
||||||
|
},
|
||||||
|
isError = isDuplicate,
|
||||||
|
modifier = Modifier.align(Alignment.CenterHorizontally)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (isDuplicate) {
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
Text(stringResource(R.string.preference_profiles_create_duplicate))
|
||||||
|
} else if (!minimumReached) {
|
||||||
|
Spacer(Modifier.size(8.dp))
|
||||||
|
|
||||||
|
Text(stringResource(R.string.preference_profiles_create_info, filename))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
ProfileManager.get(context).storeProfile(Profile(filename, enteredValue))
|
||||||
|
onDismissRequest()
|
||||||
|
},
|
||||||
|
enabled = !minimumReached && !isDuplicate
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.preference_profiles_create_action))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.message_box_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BaseDialog(
|
||||||
|
title: String,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
dismissButton: @Composable () -> Unit,
|
||||||
|
confirmButton: @Composable () -> Unit,
|
||||||
|
content: @Composable (ColumnScope.() -> Unit),
|
||||||
|
) {
|
||||||
|
Dialog(onDismissRequest = onDismissRequest) {
|
||||||
|
Card(
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
colors = CardDefaults.cardColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// styling a dialog is actually a little hard if you're doing what i'm doing
|
||||||
|
// maybe there's a better way to make these padding values...
|
||||||
|
Column(modifier = Modifier.verticalScroll(rememberScrollState())) {
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style = Typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(
|
||||||
|
start = 28.dp,
|
||||||
|
top = 24.dp,
|
||||||
|
bottom = 12.dp
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
content()
|
||||||
|
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.End,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
bottom = 16.dp,
|
||||||
|
end = 16.dp,
|
||||||
|
top = 4.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
dismissButton()
|
||||||
|
confirmButton()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val LocalSelectValue = compositionLocalOf<Any> { 0 }
|
||||||
|
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> SelectOption(name: String, value: T, enabled: Boolean = true, leadingContent: @Composable (Boolean) -> Unit = {}) {
|
||||||
|
val currentValue = LocalSelectValue.current
|
||||||
|
val setValue = LocalSelectSetValue.current
|
||||||
|
|
||||||
|
val isSelected = currentValue.equals(value)
|
||||||
|
|
||||||
|
// do not give the row or column padding!! it messes up the selection effect
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(
|
||||||
|
onClick = { setValue(value as Any) },
|
||||||
|
role = Role.RadioButton,
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
.padding(horizontal = 12.dp)
|
||||||
|
) {
|
||||||
|
Row(modifier = Modifier.weight(1.0f, true), verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
RadioButton(
|
||||||
|
selected = isSelected,
|
||||||
|
onClick = { setValue(value as Any) },
|
||||||
|
enabled = enabled
|
||||||
|
)
|
||||||
|
Text(name, style = Typography.bodyMedium)
|
||||||
|
}
|
||||||
|
|
||||||
|
leadingContent(isSelected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun <T> SelectDialog(
|
||||||
|
title: String,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onSelect: (T) -> Unit,
|
||||||
|
initialValue: T,
|
||||||
|
options: @Composable () -> Unit,
|
||||||
|
) {
|
||||||
|
val (selectedValue, setSelectedValue) = remember { mutableStateOf(initialValue) }
|
||||||
|
|
||||||
|
BaseDialog(
|
||||||
|
title = title,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
dismissButton = {
|
||||||
|
TextButton(onClick = onDismissRequest) {
|
||||||
|
Text(stringResource(R.string.message_box_cancel))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
confirmButton = {
|
||||||
|
TextButton(onClick = { onSelect(selectedValue) }) {
|
||||||
|
Text(stringResource(R.string.message_box_accept))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalSelectValue provides (selectedValue as Any),
|
||||||
|
LocalSelectSetValue provides ({
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
setSelectedValue(it as T)
|
||||||
|
})
|
||||||
|
) {
|
||||||
|
options()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user