Skip to content

Commit 071cf40

Browse files
committed
initial profiles support
1 parent 19f47f1 commit 071cf40

11 files changed

+422
-15
lines changed

app/src/main/java/com/geode/launcher/UserDirectoryProvider.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class UserDirectoryProvider : DocumentsProvider() {
4040

4141
override fun onCreate(): Boolean {
4242
val context = context ?: return false
43-
rootDir = LaunchUtils.getBaseDirectory(context)
43+
rootDir = LaunchUtils.getBaseDirectory(context, true)
4444

4545
return true
4646
}

app/src/main/java/com/geode/launcher/preferences/DeveloperSettingsActivity.kt

+5
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ fun DeveloperSettingsScreen(onBackPressedDispatcher: OnBackPressedDispatcher?) {
174174
preferenceKey = PreferenceUtils.Key.ENABLE_REDESIGN,
175175
)
176176
}
177+
178+
OptionsGroup(title = stringResource(R.string.preference_profiles)) {
179+
ProfileCreateCard()
180+
ProfileSelectCard()
181+
}
177182
}
178183
}
179184
}

app/src/main/java/com/geode/launcher/preferences/SettingsComponents.kt

+224-8
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.geode.launcher.preferences
22

33
import android.content.Context
4+
import android.content.Intent
5+
import androidx.activity.compose.LocalActivity
46
import androidx.compose.foundation.clickable
57
import androidx.compose.foundation.layout.Arrangement
68
import androidx.compose.foundation.layout.Box
@@ -19,6 +21,7 @@ import androidx.compose.foundation.verticalScroll
1921
import androidx.compose.material.icons.Icons
2022
import androidx.compose.material.icons.filled.Add
2123
import androidx.compose.material.icons.filled.Clear
24+
import androidx.compose.material.icons.filled.Delete
2225
import androidx.compose.material3.AlertDialog
2326
import androidx.compose.material3.Card
2427
import androidx.compose.material3.Icon
@@ -32,9 +35,11 @@ import androidx.compose.material3.Text
3235
import androidx.compose.material3.TextButton
3336
import androidx.compose.runtime.Composable
3437
import androidx.compose.runtime.CompositionLocalProvider
35-
import androidx.compose.runtime.compositionLocalOf
38+
import androidx.compose.runtime.collectAsState
3639
import androidx.compose.runtime.getValue
40+
import androidx.compose.runtime.compositionLocalOf
3741
import androidx.compose.runtime.mutableFloatStateOf
42+
import androidx.compose.runtime.mutableStateListOf
3843
import androidx.compose.runtime.mutableStateOf
3944
import androidx.compose.runtime.remember
4045
import androidx.compose.runtime.setValue
@@ -55,10 +60,13 @@ import com.geode.launcher.ui.theme.GeodeLauncherTheme
5560
import com.geode.launcher.ui.theme.Typography
5661
import com.geode.launcher.utils.LabelledText
5762
import com.geode.launcher.utils.PreferenceUtils
63+
import com.geode.launcher.utils.Profile
64+
import com.geode.launcher.utils.ProfileManager
5865
import kotlin.math.log10
5966
import kotlin.math.max
6067
import kotlin.math.min
6168
import kotlin.math.roundToInt
69+
import kotlin.system.exitProcess
6270

6371

6472
fun toggleSetting(context: Context, preferenceKey: PreferenceUtils.Key): Boolean {
@@ -556,26 +564,234 @@ internal val LocalSelectValue = compositionLocalOf<Any> { 0 }
556564
internal val LocalSelectSetValue = staticCompositionLocalOf<(Any) -> Unit> { {} }
557565

558566
@Composable
559-
fun <T> SelectOption(name: String, value: T) {
567+
fun <T> SelectOption(name: String, value: T, enabled: Boolean = true, leadingContent: @Composable (Boolean) -> Unit = {}) {
560568
val currentValue = LocalSelectValue.current
561569
val setValue = LocalSelectSetValue.current
562570

571+
val isSelected = currentValue.equals(value)
572+
563573
// do not give the row or column padding!! it messes up the selection effect
564574
Row(
565575
verticalAlignment = Alignment.CenterVertically,
566576
modifier = Modifier
567577
.fillMaxWidth()
568578
.clickable(
569579
onClick = { setValue(value as Any) },
570-
role = Role.RadioButton
580+
role = Role.RadioButton,
581+
enabled = enabled
571582
)
572583
.padding(horizontal = 12.dp)
573584
) {
574-
RadioButton(
575-
selected = currentValue.equals(value),
576-
onClick = { setValue(value as Any) }
577-
)
578-
Text(name, style = Typography.bodyMedium)
585+
Row(modifier = Modifier.weight(1.0f, true), verticalAlignment = Alignment.CenterVertically) {
586+
RadioButton(
587+
selected = isSelected,
588+
onClick = { setValue(value as Any) },
589+
enabled = enabled
590+
)
591+
Text(name, style = Typography.bodyMedium)
592+
}
593+
594+
leadingContent(isSelected)
595+
}
596+
}
597+
598+
@Composable
599+
fun ProfileCreateDialog(onDismissRequest: () -> Unit) {
600+
var enteredValue by remember { mutableStateOf("") }
601+
602+
val filename = enteredValue.take(16)
603+
.lowercase()
604+
.map {
605+
if ("qwertyuiopasdfghjklzxcvbnm1234567890-_.".contains(it))
606+
it else '_'
607+
}
608+
.joinToString("")
609+
610+
611+
val context = LocalContext.current
612+
613+
val minimumReached = enteredValue.isEmpty() || filename.isEmpty()
614+
615+
val profileManager = ProfileManager.get(context)
616+
val currentProfiles = remember {
617+
profileManager.getProfiles().map { it.path }.toSet()
618+
}
619+
620+
val isDuplicate = currentProfiles.contains(filename)
621+
622+
AlertDialog(
623+
onDismissRequest = { onDismissRequest() },
624+
title = {
625+
Text(stringResource(R.string.preference_profiles_create))
626+
},
627+
text = {
628+
Column {
629+
Row(
630+
verticalAlignment = Alignment.CenterVertically,
631+
modifier = Modifier.weight(1.0f, false)
632+
) {
633+
OutlinedTextField(
634+
value = enteredValue,
635+
onValueChange = {
636+
enteredValue = it
637+
},
638+
singleLine = true,
639+
keyboardOptions = KeyboardOptions(
640+
keyboardType = KeyboardType.Text,
641+
),
642+
label = {
643+
Text(stringResource(R.string.preference_profiles_create_name))
644+
},
645+
isError = isDuplicate
646+
)
647+
}
648+
649+
if (isDuplicate) {
650+
Spacer(Modifier.size(8.dp))
651+
652+
Text(stringResource(R.string.preference_profiles_create_duplicate))
653+
} else if (!minimumReached) {
654+
Spacer(Modifier.size(8.dp))
655+
656+
Text(stringResource(R.string.preference_profiles_create_info, filename))
657+
}
658+
}
659+
},
660+
confirmButton = {
661+
TextButton(
662+
onClick = {
663+
ProfileManager.get(context).storeProfile(Profile(filename, enteredValue))
664+
onDismissRequest()
665+
},
666+
enabled = !minimumReached && !isDuplicate
667+
) {
668+
Text(stringResource(R.string.preference_profiles_create_action))
669+
}
670+
},
671+
dismissButton = {
672+
TextButton(onClick = onDismissRequest) {
673+
Text(stringResource(R.string.message_box_cancel))
674+
}
675+
},
676+
)
677+
}
678+
679+
@Composable
680+
fun ProfileCreateCard() {
681+
var showDialog by remember { mutableStateOf(false) }
682+
683+
OptionsButton(
684+
title = stringResource(R.string.preference_profiles_create),
685+
icon = {
686+
Icon(painterResource(R.drawable.icon_person_add), contentDescription = null)
687+
}
688+
) {
689+
showDialog = true
690+
}
691+
692+
if (showDialog) {
693+
ProfileCreateDialog(onDismissRequest = {
694+
showDialog = false
695+
})
696+
}
697+
}
698+
699+
@Composable
700+
fun ProfileSelectCard() {
701+
val context = LocalContext.current
702+
val profileManager = ProfileManager.get(context)
703+
704+
val currentProfileId = remember {
705+
profileManager.getCurrentProfile()
706+
}
707+
708+
var currentProfileName = remember {
709+
val savedProfile = currentProfileId
710+
if (savedProfile == null) {
711+
"Default"
712+
} else {
713+
profileManager.getProfile(savedProfile)
714+
?.name ?: savedProfile
715+
}
716+
}
717+
718+
val clearedProfiles = remember { mutableStateListOf<String>() }
719+
720+
val profileList by profileManager.storedProfiles.collectAsState()
721+
722+
var showDialog by remember { mutableStateOf(false) }
723+
724+
OptionsButton(
725+
title = stringResource(R.string.preference_profiles_select),
726+
description = stringResource(R.string.preference_profiles_current, currentProfileName)
727+
) {
728+
showDialog = true
729+
}
730+
731+
val activity = LocalActivity.current
732+
if (showDialog) {
733+
SelectDialog(
734+
title = stringResource(R.string.preference_profiles_select),
735+
onDismissRequest = {
736+
showDialog = false
737+
},
738+
onSelect = {
739+
showDialog = false
740+
741+
val selectedProfileId = it.takeIf { it.isNotEmpty() }
742+
743+
if (clearedProfiles.isNotEmpty()) {
744+
profileManager.deleteProfiles(clearedProfiles)
745+
}
746+
747+
if (currentProfileId != selectedProfileId) {
748+
profileManager
749+
.setCurrentProfile(selectedProfileId)
750+
751+
activity?.run {
752+
packageManager.getLaunchIntentForPackage(packageName)?.also {
753+
val mainIntent = Intent.makeRestartActivityTask(it.component)
754+
startActivity(mainIntent)
755+
exitProcess(0)
756+
}
757+
}
758+
}
759+
},
760+
initialValue = currentProfileId ?: "",
761+
) {
762+
SelectOption("Default", "")
763+
764+
profileList.forEach { profile ->
765+
val path = profile.path
766+
767+
SelectOption(
768+
stringResource(R.string.preference_profiles_select_value, profile.name, path),
769+
path,
770+
enabled = !clearedProfiles.contains(path),
771+
leadingContent = { selected ->
772+
IconButton(onClick = {
773+
if (clearedProfiles.contains(path)) {
774+
clearedProfiles.remove(path)
775+
} else {
776+
clearedProfiles.add(path)
777+
}
778+
}, enabled = !selected) {
779+
if (clearedProfiles.contains(path)) {
780+
Icon(
781+
painterResource(R.drawable.icon_undo),
782+
contentDescription = stringResource(R.string.preference_profiles_delete_undo_alt)
783+
)
784+
} else {
785+
Icon(
786+
Icons.Filled.Delete,
787+
contentDescription = stringResource(R.string.preference_profiles_delete_alt)
788+
)
789+
}
790+
}
791+
}
792+
)
793+
}
794+
}
579795
}
580796
}
581797

app/src/main/java/com/geode/launcher/utils/GeodeUtils.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -524,7 +524,7 @@ object GeodeUtils {
524524
return false
525525
}
526526

527-
return capabilityListener.get()?.onCapabilityAdded(capability) ?: false
527+
return capabilityListener.get()?.onCapabilityAdded(capability) == true
528528
}
529529

530530
external fun nativeKeyUp(keyCode: Int, modifiers: Int)

app/src/main/java/com/geode/launcher/utils/LaunchUtils.kt

+9-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ object LaunchUtils {
4747
/**
4848
* Returns the directory that Geode/the game should base itself off of.
4949
*/
50-
fun getBaseDirectory(context: Context): File {
50+
fun getBaseDirectory(context: Context, ignoreProfile: Boolean = false): File {
5151
// deprecated, but seems to be the best choice of directory (i forced mat to test it)
5252
// also, is getting the first item the correct choice here?? what do they mean
5353
@Suppress("DEPRECATION")
@@ -58,6 +58,14 @@ object LaunchUtils {
5858
val noMediaPath = File(dir, ".nomedia")
5959
noMediaPath.createNewFile()
6060

61+
val currentProfile = ProfileManager.get(context).getCurrentProfile()
62+
if (currentProfile != null && !ignoreProfile) {
63+
val profile = File(dir, "profiles/$currentProfile/")
64+
profile.mkdirs()
65+
66+
return profile
67+
}
68+
6169
return dir
6270
}
6371

app/src/main/java/com/geode/launcher/utils/PreferenceUtils.kt

+5-4
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,6 @@ import com.geode.launcher.BuildConfig
2020
*/
2121
class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
2222
companion object {
23-
private const val FILE_KEY = "GeodeLauncherPreferencesFileKey"
24-
2523
@Composable
2624
fun useBooleanPreference(preferenceKey: Key, lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current): MutableState<Boolean> {
2725
return usePreference(
@@ -70,7 +68,9 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
7068
preferenceSet: (PreferenceUtils, Key, T) -> Unit
7169
): MutableState<T> {
7270
val context = LocalContext.current
73-
val sharedPreferences = context.getSharedPreferences(FILE_KEY, Context.MODE_PRIVATE)
71+
72+
val currentProfile = remember { ProfileManager.get(context).getCurrentFileKey() }
73+
val sharedPreferences = context.getSharedPreferences(currentProfile, Context.MODE_PRIVATE)
7474

7575
val preferences = get(sharedPreferences)
7676

@@ -114,7 +114,8 @@ class PreferenceUtils(private val sharedPreferences: SharedPreferences) {
114114
}
115115

116116
fun get(context: Context): PreferenceUtils {
117-
val sharedPreferences = context.getSharedPreferences(FILE_KEY, Context.MODE_PRIVATE)
117+
val currentFileKey = ProfileManager.get(context).getCurrentFileKey()
118+
val sharedPreferences = context.getSharedPreferences(currentFileKey, Context.MODE_PRIVATE)
118119
return get(sharedPreferences)
119120
}
120121

0 commit comments

Comments
 (0)