Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4223918
Add Steam Friends, messaging, images, achievements, launch options, j…
maxjivi05 Jun 5, 2026
9f557ea
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 6, 2026
c93bf0a
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 6, 2026
e47104d
Add Steam Friends, messaging, images, achievements, launch options, j…
maxjivi05 Jun 5, 2026
05ce629
Merge upstream main into steam-features (restore upstream merges; kee…
maxjivi05 Jun 6, 2026
d8804d8
Merge branch 'main' into steam-features
maxjivi05 Jun 6, 2026
c4223bf
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 7, 2026
c49db14
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 8, 2026
6c3187e
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 8, 2026
238ff69
Add Steam chat notifications, chat heads, and in-game chat
maxjivi05 Jun 8, 2026
56ed350
Fix tap dead zone over Add Custom Game FAB and pin chat-head overlay …
maxjivi05 Jun 9, 2026
3854419
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 9, 2026
318bb5b
feat(steam): Launch Options selector in the STEAM dropdown
claude Jun 9, 2026
bbcfcbb
fix(steam): show Launch Options on the Library game screen too
claude Jun 10, 2026
99039c4
ui(steam): Workshop-style window for the Launch Options picker
claude Jun 10, 2026
cf4560d
review(steam): launch options cleanup pass
claude Jun 10, 2026
71b4263
feat(steam): beta branch selector in the STEAM dropdown
claude Jun 10, 2026
ba8d085
fix(steam): branch selector update check/download honour selected branch
claude Jun 10, 2026
658a5b1
fix(steam): lightweight selectedBranch read — stop loadShortcuts in d…
claude Jun 10, 2026
d837242
review(steam): beta branch selector — multi-angle review fixes
claude Jun 10, 2026
c002d6f
perf(steam): read launch-option selection without constructing Shortcuts
claude Jun 10, 2026
fbbce70
Merge launch-options lightweight shortcut reader
claude Jun 10, 2026
950f2c8
fix(steam): crash-proof the store dialog's data-loading effects
claude Jun 10, 2026
f5ee2cf
fix(steam): VerifyError on store game tap — shrink StoreGameDetailScr…
claude Jun 10, 2026
a8aabc3
fix(steam): split StoreGameDetailScreen prologue from body for ART ve…
claude Jun 10, 2026
45f8d4d
Merge branch 'WinNative-Emu:main' into steam-features
maxjivi05 Jun 10, 2026
b75d980
review(steam): final cleanup pass over launch options + beta branches
claude Jun 11, 2026
2344324
Merge branch 'claude/steam-beta-branch-selector-ckipz0' into claude/w…
claude Jun 11, 2026
6faf916
Merge maxjivi05/steam-features into beta-branch-selector for compatib…
claude Jun 11, 2026
95707c0
feat(steam): pick beta branch before downloading — split Download button
claude Jun 11, 2026
935cc93
fix(steam): recover beta-branch selection lost to an app reinstall
claude Jun 11, 2026
f78e0fc
review(steam): multi-angle review fixes for pre-download selector + r…
claude Jun 11, 2026
c839a4c
ui(steam): one continuous gradient across the split Download pill
claude Jun 12, 2026
ee4d41f
Merge remote-tracking branch 'origin/main' into steam-beta-branch-sel…
claude Jun 12, 2026
9a1c2d6
Merge branch 'steam-beta-branch-selector' into winnative-fork-steam-m…
claude Jun 12, 2026
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/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />
<uses-permission android:name="android.permission.HIGH_SAMPLING_RATE_SENSORS" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
Expand Down Expand Up @@ -125,6 +126,18 @@
android:exported="false"
android:foregroundServiceType="dataSync" />

<service
android:name="com.winlator.cmod.feature.stores.steam.chat.ChatOverlayService"
android:enabled="true"
android:exported="false" />

<activity
android:name="com.winlator.cmod.feature.stores.steam.chat.ChatImagePickerActivity"
android:theme="@android:style/Theme.Translucent.NoTitleBar"
android:excludeFromRecents="true"
android:noHistory="true"
android:exported="false" />

<service
android:name="com.winlator.cmod.feature.stores.epic.service.EpicService"
android:enabled="true"
Expand Down
271 changes: 271 additions & 0 deletions app/src/main/app/shell/BetaBranchScreen.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
package com.winlator.cmod.app.shell

import androidx.compose.foundation.BorderStroke
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.AltRoute
import androidx.compose.material.icons.outlined.Check
import androidx.compose.material.icons.outlined.Close
import androidx.compose.material.icons.outlined.Lock
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.winlator.cmod.R
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale

/** A single Steam beta branch entry from appinfo depots.branches. */
internal data class StoreBetaBranchItem(
val name: String,
val buildId: Long,
val timeUpdated: Date?,
val pwdRequired: Boolean,
)

// Palette — mirrors the LaunchOptions / Workshop window so the modal feels native.
private val BbBg = Color(0xFF12121B)
private val BbBorder = Color(0xFF2A2A3A)
private val BbAccent = Color(0xFF1A9FFF)
private val BbAccentGlow = Color(0xFF58A6FF)
private val BbTextPrimary = Color(0xFFF0F4FF)
private val BbTextSecondary = Color(0xFF93A6BC)
private val BbScrim = Color(0xFF000000)
private val BbLocked = Color(0xFF505060)

/**
* Steam beta-branch picker — a Workshop-shaped modal window listing the game's
* PICS depots.branches entries. Tapping an unlocked row persists the selection,
* closes the window and triggers the update flow so the branch's build actually
* downloads. Password-protected branches are shown as disabled (no
* beta-password support in this app).
*
* Stateless: data and callbacks are hoisted to the BetaBranchesDialog wrapper.
*/
@Composable
internal fun StoreBetaBranchScreen(
gameTitle: String,
branches: List<StoreBetaBranchItem>,
selectedBranch: StoreBetaBranchItem?,
onSelect: (StoreBetaBranchItem) -> Unit,
onClose: () -> Unit,
) {
BoxWithConstraints(
modifier =
Modifier
.fillMaxSize()
.background(BbScrim.copy(alpha = 0.6f))
.windowInsetsPadding(WindowInsets.navigationBars),
contentAlignment = Alignment.Center,
) {
val dialogWidth = (maxWidth - 32.dp).coerceAtMost(560.dp)
val dialogMaxHeight = (maxHeight - 48.dp).coerceIn(220.dp, 640.dp)
Surface(
modifier =
Modifier
.widthIn(min = 320.dp, max = dialogWidth)
.fillMaxWidth()
.heightIn(max = dialogMaxHeight),
shape = RoundedCornerShape(14.dp),
color = BbBg,
border = BorderStroke(1.dp, BbBorder),
tonalElevation = 8.dp,
) {
Column(Modifier.fillMaxWidth()) {
BetaBranchHeader(
gameTitle = gameTitle,
branchCount = branches.size,
onClose = onClose,
)
HorizontalDivider(color = BbBorder, thickness = 0.5.dp)
LazyColumn(
modifier = Modifier.fillMaxWidth().weight(1f, fill = false),
contentPadding = PaddingValues(vertical = 4.dp),
) {
itemsIndexed(branches) { index, branch ->
BetaBranchPickerRow(
branch = branch,
selected = branch == selectedBranch,
onClick = { onSelect(branch) },
)
if (index < branches.lastIndex) {
HorizontalDivider(
color = Color.White.copy(alpha = 0.06f),
thickness = 1.dp,
modifier = Modifier.padding(horizontal = 14.dp),
)
}
}
}
}
}
}
}

@Composable
private fun BetaBranchHeader(
gameTitle: String,
branchCount: Int,
onClose: () -> Unit,
) {
Row(
modifier = Modifier.fillMaxWidth().padding(start = 16.dp, end = 8.dp, top = 10.dp, bottom = 10.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(10.dp),
) {
Box(
Modifier
.size(34.dp)
.clip(RoundedCornerShape(9.dp))
.background(BbAccent.copy(alpha = 0.16f)),
contentAlignment = Alignment.Center,
) {
Icon(
Icons.Outlined.AltRoute,
contentDescription = null,
tint = BbAccentGlow,
modifier = Modifier.size(19.dp),
)
}
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(1.dp)) {
Text(
stringResource(R.string.store_game_beta_branch).uppercase(),
color = BbTextSecondary,
fontSize = 9.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 0.9.sp,
)
Text(
gameTitle,
style = MaterialTheme.typography.titleSmall,
color = BbTextPrimary,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
Surface(
modifier =
Modifier.semantics {
contentDescription = "$branchCount branches"
},
color = BbAccent.copy(alpha = 0.14f),
shape = RoundedCornerShape(7.dp),
) {
Text(
branchCount.toString(),
color = BbAccentGlow,
fontSize = 11.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(horizontal = 9.dp, vertical = 3.dp),
)
}
IconButton(onClick = onClose, modifier = Modifier.size(36.dp)) {
Icon(
Icons.Outlined.Close,
contentDescription = "Close",
tint = BbTextSecondary,
modifier = Modifier.size(20.dp),
)
}
}
}

@Composable
private fun BetaBranchPickerRow(
branch: StoreBetaBranchItem,
selected: Boolean,
onClick: () -> Unit,
) {
val rowAlpha = if (branch.pwdRequired) 0.45f else 1f
Row(
modifier =
Modifier
.fillMaxWidth()
.then(if (!branch.pwdRequired) Modifier.clickable(onClick = onClick) else Modifier)
.alpha(rowAlpha)
.padding(horizontal = 14.dp, vertical = 11.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(11.dp),
) {
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(2.dp)) {
val displayName =
if (branch.name.equals("public", ignoreCase = true)) {
"${branch.name} (default)"
} else {
branch.name
}
Text(
displayName,
color = if (selected) BbAccentGlow else BbTextPrimary,
fontSize = 13.sp,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
val dateStr =
remember(branch.timeUpdated) {
branch.timeUpdated
?.let { SimpleDateFormat("MMM d, yyyy", Locale.US).format(it) }
?: "—"
}
Text(
"build ${branch.buildId} · $dateStr",
color = BbTextSecondary,
fontSize = 11.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
when {
branch.pwdRequired -> Icon(
Icons.Outlined.Lock,
contentDescription = null,
tint = BbLocked,
modifier = Modifier.size(17.dp),
)
selected -> Icon(
Icons.Outlined.Check,
contentDescription = null,
tint = BbAccentGlow,
modifier = Modifier.size(18.dp),
)
}
}
}
Loading