Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions app/src/main/feature/settings/containers/ContainersFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import com.winlator.cmod.runtime.container.ContainerManager
import com.winlator.cmod.runtime.content.ContentsManager
import com.winlator.cmod.runtime.display.XServerDisplayActivity
import com.winlator.cmod.runtime.display.environment.ImageFs
import com.winlator.cmod.feature.setup.SetupWizardActivity
import com.winlator.cmod.shared.ui.toast.WinToast
import com.winlator.cmod.shared.io.FileUtils
import com.winlator.cmod.shared.ui.dialog.ContainerProgressPopup
Expand Down Expand Up @@ -67,6 +68,7 @@ class ContainersFragment : Fragment() {
onInstallComponents = ::openComponentInstaller,
onRemoveContainer = ::removeContainer,
onShowInfo = ::showContainerInfo,
onSetDefaultContainer = ::setDefaultContainer,
onDismissDialog = ::dismissDialog,
onConfirmDuplicateDialog = ::performDuplicateContainer,
onConfirmRemoveDialog = ::performRemoveContainer,
Expand Down Expand Up @@ -99,6 +101,7 @@ class ContainersFragment : Fragment() {
screenState.copy(
containers = all.filter { !it.isGamescopeRuntime },
gamescope = all.filter { it.isGamescopeRuntime },
defaultContainerId = SetupWizardActivity.getDefaultContainerId(context),
)
}

Expand Down Expand Up @@ -182,6 +185,16 @@ class ContainersFragment : Fragment() {
startStorageScan(container)
}

private fun setDefaultContainer(container: Container) {
val ctx = context ?: return
SetupWizardActivity.saveDefaultContainerId(ctx, container.id)
loadContainersList()
WinToast.show(
ctx,
getString(R.string.containers_set_default_success, container.name),
)
}

private fun performDuplicateContainer(container: Container) {
dismissDialog()
val duplicatingPopup = ContainerProgressPopup(
Expand Down
73 changes: 63 additions & 10 deletions app/src/main/feature/settings/containers/ContainersScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ private const val ContainerCardAspect = 1.2f

data class ContainersScreenState(
val containers: List<Container> = emptyList(),
val defaultContainerId: Int = 0,
/** The GameScope containers; Linux programs and Steam boot into the first of them. */
val gamescope: List<Container> = emptyList(),
val dialog: ContainersDialogUiState = ContainersDialogUiState.None,
Expand Down Expand Up @@ -141,6 +142,7 @@ fun ContainersScreen(
onInstallComponents: (Container) -> Unit,
onRemoveContainer: (Container) -> Unit,
onShowInfo: (Container) -> Unit,
onSetDefaultContainer: (Container) -> Unit,
onDismissDialog: () -> Unit,
onConfirmDuplicateDialog: (Container) -> Unit,
onConfirmRemoveDialog: (Container) -> Unit,
Expand Down Expand Up @@ -205,16 +207,31 @@ fun ContainersScreen(
cellIndex <= state.containers.size -> {
val container = state.containers[cellIndex - 1]
key(container.id) {
val isDefaultContainer =
state.defaultContainerId > 0 &&
container.id == state.defaultContainerId
val defaultArchLabel =
if (isDefaultContainer) {
if (container.getWineVersion().contains("arm64ec", ignoreCase = true)) {
stringResource(R.string.container_config_arch_arm64)
} else {
stringResource(R.string.container_config_arch_x86_64)
}
} else {
null
}
ContainerCard(
container = container,
navRow = gyBase,
navCol = gxBase,
defaultArchLabel = defaultArchLabel,
onRun = { onRunContainer(container) },
onEdit = { onEditContainer(container) },
onDuplicate = { onDuplicateContainer(container) },
onInstallComponents = { onInstallComponents(container) },
onRemove = { onRemoveContainer(container) },
onShowInfo = { onShowInfo(container) },
onSetDefault = { onSetDefaultContainer(container) },
)
}
}
Expand Down Expand Up @@ -262,6 +279,8 @@ fun ContainersScreen(
onInstallComponents = null,
onRemove = { onRemoveContainer(gamescope) },
onShowInfo = { onShowInfo(gamescope) },
defaultArchLabel = null,
onSetDefault = null,
)
}
}
Expand Down Expand Up @@ -458,6 +477,8 @@ private fun ContainerCard(
onInstallComponents: (() -> Unit)?,
onRemove: () -> Unit,
onShowInfo: () -> Unit,
defaultArchLabel: String?,
onSetDefault: (() -> Unit)?,
) {
var menuExpanded by remember { mutableStateOf(false) }
val nameFontSize =
Expand Down Expand Up @@ -521,6 +542,15 @@ private fun ContainerCard(
},
)
}
if (onSetDefault != null) {
DropdownMenuItem(
text = { Text(stringResource(R.string.containers_set_default), color = ContainersTextPrimary) },
onClick = {
menuExpanded = false
onSetDefault()
},
)
}
DropdownMenuItem(
text = { Text(stringResource(R.string.container_config_storage_info), color = ContainersTextPrimary) },
onClick = {
Expand All @@ -546,17 +576,40 @@ private fun ContainerCard(
.weight(1f),
contentAlignment = Alignment.Center,
) {
Text(
text = container.name,
color = ContainersTextPrimary,
fontSize = nameFontSize,
fontWeight = FontWeight.Bold,
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
Column(
modifier = Modifier.fillMaxWidth(),
)
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = container.name,
color = ContainersTextPrimary,
fontSize = nameFontSize,
fontWeight = FontWeight.Bold,
maxLines = 1,
softWrap = false,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
modifier = Modifier.fillMaxWidth(),
)
if (defaultArchLabel != null) {
Spacer(Modifier.height(4.dp))
Box(
modifier =
Modifier
.clip(RoundedCornerShape(6.dp))
.background(ContainersAccent.copy(alpha = 0.16f))
.border(1.dp, ContainersAccent.copy(alpha = 0.5f), RoundedCornerShape(6.dp))
.padding(horizontal = 8.dp, vertical = 2.dp),
) {
Text(
text = stringResource(R.string.containers_default_badge, defaultArchLabel),
color = ContainersAccent,
fontSize = 10.sp,
fontWeight = FontWeight.SemiBold,
)
}
}
}
}

Row(
Expand Down
114 changes: 83 additions & 31 deletions app/src/main/feature/setup/SetupWizardActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
private const val KEY_DRIVERS_VISITED = "drivers_visited"
private const val KEY_DEFAULT_X86_CONTAINER_ID = "default_x86_container_id"
private const val KEY_DEFAULT_ARM64_CONTAINER_ID = "default_arm64_container_id"
private const val KEY_DEFAULT_CONTAINER_ID = "default_container_id"
private const val KEY_DEFAULT_X86_SETTINGS_DONE = "default_x86_settings_done"
private const val KEY_DEFAULT_ARM64_SETTINGS_DONE = "default_arm64_settings_done"
private const val KEY_LAST_DRIVER_ID = "last_driver_id"
Expand Down Expand Up @@ -259,35 +260,32 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
): Container? {
val contentsManager = ContentsManager(context)
contentsManager.syncContents()
val preferredId = getDefaultX86ContainerId(context)
if (preferredId > 0) {
containerManager.getContainerById(preferredId)?.let {
// 0 means unset (RETRO_CONTAINER_ID is 0).
val defaultId = getDefaultContainerId(context)
if (defaultId > 0) {
containerManager.getContainerById(defaultId)?.let {
if (isContainerUsable(contentsManager, it)) return it
}
}
return containerManager.containers.firstOrNull { isContainerUsable(contentsManager, it) }
}

/** Falls back to the legacy per-architecture keys so pre-unified defaults survive. */
@JvmStatic
fun getDefaultX86ContainerId(context: Context): Int = prefs(context).getInt(KEY_DEFAULT_X86_CONTAINER_ID, 0)

@JvmStatic
fun getDefaultArm64ContainerId(context: Context): Int = prefs(context).getInt(KEY_DEFAULT_ARM64_CONTAINER_ID, 0)

@JvmStatic
fun saveDefaultX86ContainerId(
context: Context,
containerId: Int,
) {
prefs(context).edit().putInt(KEY_DEFAULT_X86_CONTAINER_ID, containerId).apply()
fun getDefaultContainerId(context: Context): Int {
val unified = prefs(context).getInt(KEY_DEFAULT_CONTAINER_ID, 0)
if (unified > 0) return unified
val x86 = prefs(context).getInt(KEY_DEFAULT_X86_CONTAINER_ID, 0)
if (x86 > 0) return x86
return prefs(context).getInt(KEY_DEFAULT_ARM64_CONTAINER_ID, 0)
}

@JvmStatic
fun saveDefaultArm64ContainerId(
fun saveDefaultContainerId(
context: Context,
containerId: Int,
) {
prefs(context).edit().putInt(KEY_DEFAULT_ARM64_CONTAINER_ID, containerId).apply()
prefs(context).edit().putInt(KEY_DEFAULT_CONTAINER_ID, containerId).apply()
}

@JvmStatic
Expand Down Expand Up @@ -550,6 +548,7 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
private val notifGranted = mutableStateOf(false)
private val notifDenied = mutableStateOf(false)
private val backgroundSessionEnabled = mutableStateOf(false)
private val defaultContainerId = mutableIntStateOf(0)

private val pageIndex = mutableIntStateOf(0)
private val navRegion = mutableIntStateOf(REGION_CONTENT)
Expand Down Expand Up @@ -973,22 +972,20 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {

val preferences = prefs(this)
val containerManager = ContainerManager(this)
val x86Container =
containerManager
.getContainerById(getDefaultX86ContainerId(this))
?.takeIf { isContainerUsable(this, it) }
val armContainer =
val defaultId = getDefaultContainerId(this)
defaultContainerId.intValue = defaultId
val defaultContainer =
containerManager
.getContainerById(getDefaultArm64ContainerId(this))
.getContainerById(defaultId)
?.takeIf { isContainerUsable(this, it) }

defaultX86ContainerName.value = x86Container?.name ?: ""
defaultArmContainerName.value = armContainer?.name ?: ""
defaultX86ContainerName.value = defaultContainer?.name ?: ""
defaultArmContainerName.value = defaultContainer?.name ?: ""

defaultX86SettingsDone.value =
preferences.getBoolean(KEY_DEFAULT_X86_SETTINGS_DONE, false) && x86Container != null
preferences.getBoolean(KEY_DEFAULT_X86_SETTINGS_DONE, false) && defaultContainer != null
defaultArmSettingsDone.value =
preferences.getBoolean(KEY_DEFAULT_ARM64_SETTINGS_DONE, false) && armContainer != null
preferences.getBoolean(KEY_DEFAULT_ARM64_SETTINGS_DONE, false) && defaultContainer != null
}

private fun hasStoragePermission(): Boolean =
Expand Down Expand Up @@ -3149,11 +3146,7 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
withContext(Dispatchers.IO) {
try {
val c = ensureContainerForProfile(profile, displayName)
if (isArm64) {
saveDefaultArm64ContainerId(this@SetupWizardActivity, c.id)
} else {
saveDefaultX86ContainerId(this@SetupWizardActivity, c.id)
}
// Wizard does not auto-assign; the user taps the card.
c
} catch (e: Exception) {
updateWizardError("Container creation failed: ${e.message}")
Expand All @@ -3171,6 +3164,23 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
existingContainer?.let { openContainerDefaultSettings(it.id, if (isArm64) "arm64" else "x86") }
Unit
}
val setAsDefault = {
existingContainer?.let { c ->
saveDefaultContainerId(this@SetupWizardActivity, c.id)
defaultContainerId.intValue = c.id
refreshWizardState()
WinToast.show(
this@SetupWizardActivity,
getString(R.string.containers_set_default_success, c.name),
)
}
Unit
}
val existing = existingContainer
val isDefault =
hasContainer && existing != null &&
existing.id == defaultContainerId.intValue &&
defaultContainerId.intValue > 0

val lastActivate = remember { mutableStateOf(activate) }
LaunchedEffect(activate) {
Expand Down Expand Up @@ -3218,6 +3228,48 @@ class SetupWizardActivity : FixedFontScaleFragmentActivity() {
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
if (hasContainer) {
Spacer(Modifier.width(6.dp))
if (isDefault) {
Box(
modifier =
Modifier
.clip(RoundedCornerShape(4.dp))
.background(completedTurquoise.copy(alpha = 0.16f))
.border(1.dp, completedTurquoise.copy(alpha = 0.55f), RoundedCornerShape(4.dp))
.padding(horizontal = 5.dp, vertical = 1.dp),
) {
Text(
text =
stringResource(
R.string.containers_default_badge,
archLabel,
),
color = completedTurquoise,
fontFamily = InterFont,
fontSize = if (compact) 7.sp else 8.sp,
fontWeight = FontWeight.SemiBold,
)
}
} else {
Text(
text = stringResource(R.string.containers_set_default),
color = turquoise,
fontFamily = InterFont,
fontSize = if (compact) 7.sp else 8.sp,
fontWeight = FontWeight.SemiBold,
modifier =
Modifier
.clip(RoundedCornerShape(4.dp))
.background(turquoise.copy(alpha = 0.14f))
.clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
) { onTap(); setAsDefault() }
.padding(horizontal = 5.dp, vertical = 1.dp),
)
}
}
}
Spacer(Modifier.height(if (compact) 1.dp else 3.dp))
Text(
Expand Down
Loading
Loading