feat: merge Games and store libraries into one Library and one game page (#1460)

This commit is contained in:
Kurt Himebauch authored and GitHub committed 2026-10-25 08:39:56 -04:00
1 parent 0cf5b42788
commit 2a69505199
62 files changed
+3240 -1803

No files matched your search

@@ -172,7 +172,6 @@ class FrontEndActivity : ComponentActivity() {
private var launcherIcon by mutableStateOf(LauncherIcon.DARK)
private var gameStoresEnabled by mutableStateOf(true)
private var gameStoresSpeedTier by mutableStateOf("fast")
private var storesOpenTab by mutableStateOf(SessionPrefs.STORES_OPEN_LIBRARY)
private var navRequest by mutableStateOf<String?>(null)
private var storesShowMature by mutableStateOf(false)
private var storesSort by mutableStateOf(SessionPrefs.STORES_SORTS.first())
@@ -773,7 +772,6 @@ class FrontEndActivity : ComponentActivity() {
launcherIcon = LauncherIcon.current(this)
gameStoresEnabled = SessionPrefs.gameStoresEnabled(this)
gameStoresSpeedTier = SessionPrefs.gameStoresSpeedTier(this)
storesOpenTab = SessionPrefs.storesOpenTab(this)
storesShowMature = SessionPrefs.storesShowMature(this)
storesSort = SessionPrefs.storesSort(this)
storesView = SessionPrefs.storesView(this)
@@ -837,7 +835,6 @@ class FrontEndActivity : ComponentActivity() {
launcherIcon = launcherIcon,
gameStoresEnabled = gameStoresEnabled,
gameStoresSpeedTier = gameStoresSpeedTier,
storesOpenTab = storesOpenTab,
navigate = navRequest,
addedGamesArt = addedGamesArt,
sgdbUserKey = sgdbUserKey,
@@ -1012,7 +1009,6 @@ class FrontEndActivity : ComponentActivity() {
applyLauncherFullscreen()
},
onGameStoresSpeedTier = { tier -> SessionPrefs.setGameStoresSpeedTier(this, tier); gameStoresSpeedTier = tier },
onStoresOpenTab = { tab -> SessionPrefs.setStoresOpenTab(this, tab); storesOpenTab = tab },
onNavigated = { navRequest = null },
onStoresShowMature = { show -> SessionPrefs.setStoresShowMature(this, show); storesShowMature = show },
onStoresSort = { sort -> SessionPrefs.setStoresSort(this, sort); storesSort = sort },
@@ -91,13 +91,6 @@ object SessionPrefs {
*/
fun storesEpicMode(context: Context): String = prefs(context).getString("storesEpicMode", "adaptive") ?: "adaptive"
fun storesOpenTab(context: Context): String =
prefs(context).getString("storesOpenTab", STORES_OPEN_LIBRARY)?.takeIf { it == STORES_OPEN_STORE } ?: STORES_OPEN_LIBRARY
fun setStoresOpenTab(context: Context, tab: String) {
prefs(context).edit().putString("storesOpenTab", if (tab == STORES_OPEN_STORE) STORES_OPEN_STORE else STORES_OPEN_LIBRARY).apply()
}
fun storesShowMature(context: Context): Boolean = prefs(context).getBoolean("storesShowMature", false)
fun setStoresShowMature(context: Context, show: Boolean) {
@@ -135,9 +128,6 @@ object SessionPrefs {
val STORES_SORTS = listOf("lastPlayed", "recentlyInstalled", "titleAsc", "titleDesc", "sizeDesc", "sizeAsc", "releaseDateDesc")
val STORES_VIEWS = listOf("cards", "capsules", "list")
const val STORES_OPEN_LIBRARY = "library"
const val STORES_OPEN_STORE = "store"
fun storesInstallTarget(context: Context): String = prefs(context).getString("storesInstallTarget", "") ?: ""
fun setStoresInstallTarget(context: Context, path: String) {
@@ -61,33 +61,38 @@ fun installedCards(store: Store, installed: List<InstalledStoreGame>, library: L
}.distinctBy { it.id }.sortedBy { it.title.lowercase() }
}
private fun releaseDateValue(item: CatalogItem): Long =
item.releaseDate.take(10).takeIf { it.length == 10 }?.let { raw ->
runCatching { java.time.LocalDate.parse(raw).toEpochDay() }.getOrDefault(0L)
private fun releaseDateValue(raw: String): Long =
raw.take(10).takeIf { it.length == 10 }?.let { day ->
runCatching { java.time.LocalDate.parse(day).toEpochDay() }.getOrDefault(0L)
} ?: 0L
fun sortCatalogItems(
items: List<CatalogItem>,
fun <T> sortGames(
items: List<T>,
sort: String,
lastPlayed: (CatalogItem) -> Long = { 0L },
installedAt: (CatalogItem) -> Long = { 0L },
size: (CatalogItem) -> Long = { it.sizeBytes },
): List<CatalogItem> = when (sort) {
"lastPlayed" -> items.sortedWith(compareByDescending(lastPlayed).thenBy { it.title.lowercase() })
"recentlyInstalled" -> items.sortedWith(compareByDescending(installedAt).thenBy { it.title.lowercase() })
"titleAsc" -> items.sortedBy { it.title.lowercase() }
"titleDesc" -> items.sortedByDescending { it.title.lowercase() }
"sizeDesc" -> items.sortedWith(compareByDescending(size).thenBy { it.title.lowercase() })
"sizeAsc" -> items.sortedWith(compareBy<CatalogItem> { size(it).takeIf { b -> b > 0 } ?: Long.MAX_VALUE }.thenBy { it.title.lowercase() })
"releaseDateDesc" -> items.sortedWith(compareByDescending(::releaseDateValue).thenBy { it.title.lowercase() })
else -> items
title: (T) -> String,
releaseDate: (T) -> String,
lastPlayed: (T) -> Long = { 0L },
installedAt: (T) -> Long = { 0L },
size: (T) -> Long = { 0L },
): List<T> {
val byTitle = compareBy<T> { title(it).lowercase() }
return when (sort) {
"lastPlayed" -> items.sortedWith(compareByDescending(lastPlayed).then(byTitle))
"recentlyInstalled" -> items.sortedWith(compareByDescending(installedAt).then(byTitle))
"titleAsc" -> items.sortedWith(byTitle)
"titleDesc" -> items.sortedWith(byTitle.reversed())
"sizeDesc" -> items.sortedWith(compareByDescending(size).then(byTitle))
"sizeAsc" -> items.sortedWith(compareBy<T> { size(it).takeIf { b -> b > 0 } ?: Long.MAX_VALUE }.then(byTitle))
"releaseDateDesc" -> items.sortedWith(compareByDescending<T> { releaseDateValue(releaseDate(it)) }.then(byTitle))
else -> items
}
}
internal fun retainCatalogOrder(items: List<CatalogItem>, previous: List<CatalogItem>): List<CatalogItem> {
internal fun <T> retainOrder(items: List<T>, previous: List<T>, key: (T) -> String): List<T> {
if (items.size != previous.size) return items
val byKey = items.associateBy { it.key }
if (previous.any { it.key !in byKey }) return items
return previous.map { byKey.getValue(it.key) }
val byKey = items.associateBy(key)
if (previous.any { key(it) !in byKey }) return items
return previous.map { byKey.getValue(key(it)) }
}
/** A detail page opens for search results too; the library wins when it already knows the product. */
@@ -14,6 +14,7 @@ import com.droiddeck.launcher.stores.download.DownloadEntry
object StoresState {
private const val TAG = "Stores"
private const val SEARCH_DELAY_MS = 400L
private const val LIBRARY_RETRY_MS = 60_000L
private val main = Handler(Looper.getMainLooper())
val accounts = mutableStateMapOf<Store, String>()
@@ -85,7 +86,7 @@ object StoresState {
main.post {
androidx.compose.runtime.snapshots.Snapshot.withMutableSnapshot {
val restored = found.toMap()
accounts.keys.toList().filter { it !in restored }.forEach { accounts.remove(it) }
accounts.keys.toList().filter { it !in restored }.forEach { accounts.remove(it); libraryAsked.remove(it) }
accounts.putAll(restored)
installed = onDisk
unfinished = partial
@@ -139,6 +140,18 @@ object StoresState {
if (force || !shelves.containsKey(store)) backend.loadShelves(app, force)
}
private val libraryAsked = java.util.concurrent.ConcurrentHashMap<Store, Long>()
/** Once per store, again after a minute when the last sync never produced a library (offline, expired sign-in). */
fun loadLibrary(context: Context, store: Store) {
if (!isSignedIn(store) || library.containsKey(store)) return
val now = android.os.SystemClock.elapsedRealtime()
val asked = libraryAsked[store]
if (asked != null && now - asked < LIBRARY_RETRY_MS) return
libraryAsked[store] = now
backends[store]?.syncLibrary(context.applicationContext, false)
}
fun preview(context: Context, store: Store) {
if (!shelves.containsKey(store)) backends[store]?.loadShelves(context.applicationContext, false)
}
@@ -165,6 +178,7 @@ object StoresState {
fun signIn(context: Context, store: Store) {
expired.remove(store)
libraryAsked.remove(store)
backends[store]?.signIn(context) ?: logLine("${store.label}: this build has no sign-in for it yet")
}
@@ -177,7 +191,7 @@ object StoresState {
else if (store != Store.XBOX || backend == null) StoreAccounts.clear(app, store)
main.post {
if (store == Store.XBOX && StoreAccounts.signedInAs(app, store) != null) return@post
accounts.remove(store); library.remove(store); status.remove(store); problems.remove(store)
accounts.remove(store); library.remove(store); status.remove(store); problems.remove(store); libraryAsked.remove(store)
logLine("${store.label}: signed out")
}
}
@@ -47,6 +47,8 @@ object EpicBackend : StoreBackend, EpicLaunchSupport {
if (!quiet) StoresState.status[store] = app.getString(R.string.stores_sync_library)
}
val result = EpicLibrary.sync(app, force) { line -> if (!quiet) StoresState.post { StoresState.status[store] = line } }
// Throttled means the cache is fresh, possibly written by another sync after this one read it.
val fresh = if (result == EpicLibrary.SyncResult.Throttled) EpicLibrary.cached(app).map { EpicLibrary.toCatalogItem(app, it) } else cached
StoresState.post {
StoresState.status.remove(store)
when (result) {
@@ -56,7 +58,7 @@ object EpicBackend : StoreBackend, EpicLaunchSupport {
}
is EpicLibrary.SyncResult.Failed -> StoresState.problems[store] = result.message
EpicLibrary.SyncResult.NotLoggedIn -> StoresState.problems[store] = StoresState.notSignedInLine(store)
EpicLibrary.SyncResult.Throttled -> if (!StoresState.library.containsKey(store)) StoresState.library[store] = cached
EpicLibrary.SyncResult.Throttled -> if (fresh.isNotEmpty()) StoresState.library[store] = mergeItems(StoresState.library[store], fresh)
EpicLibrary.SyncResult.Busy -> {}
}
}
@@ -227,12 +227,6 @@ object EpicClient {
)
}
fun withLauncher(games: List<Library.SteamGame>, launcher: Library.SteamGame?): List<Library.SteamGame> {
if (launcher == null) return games
val at = games.indexOfFirst { it.source == Store.EPIC.id }.takeIf { it >= 0 } ?: games.size
return games.subList(0, at) + launcher + games.subList(at, games.size)
}
private fun steamSession(): Boolean =
SessionState.running && !SessionState.stopRequested && SessionState.mode == SessionService.MODE_STEAM
@@ -51,7 +51,7 @@ object XboxBackend : StoreBackend {
if (current == generation) {
if (cached != null) StoresState.library[store] = mergeItems(StoresState.library[store], cached.games)
StoresState.problems.remove(store)
if (cached == null || force) StoresState.status[store] = "Syncing Xbox library…"
if (cached == null || force) StoresState.status[store] = app.getString(com.droiddeck.launcher.R.string.stores_sync_library)
else StoresState.status.remove(store)
}
}
@@ -0,0 +1,136 @@
package com.droiddeck.launcher.ui
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clipToBounds
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.positionInRoot
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.delay
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
/** A grid of cards with a game page that floods out of the opened card and drains back into it. */
@OptIn(androidx.compose.ui.ExperimentalComposeUiApi::class)
@Composable
internal fun CardDetailHost(
open: String?,
detailScroll: ScrollState,
gridScrolled: () -> Boolean,
onDetailFocus: (Boolean) -> Unit,
modifier: Modifier = Modifier,
wrap: @Composable (key: String, content: @Composable () -> Unit) -> Unit = { _, content -> content() },
grid: @Composable () -> Unit,
detail: @Composable (key: String) -> Unit,
) {
val ctx = LocalContext.current
val ff = LocalFrontFocus.current
val density = LocalDensity.current
var shown by remember { mutableStateOf<String?>(null) }
var leaving by remember { mutableStateOf(false) }
var from by remember { mutableStateOf<Origin?>(null) }
var art by remember { mutableStateOf<String?>(null) }
var viewportAt by remember { mutableStateOf(Offset.Zero) }
LaunchedEffect(open) {
if (open != null) {
val mark = StoresMotion.takeCard(open)
from = mark?.let { Origin(it.bounds.translate(-viewportAt), it.corner) }
art = mark?.art
StoresMotion.heroLands = mark != null
leaving = false
shown = open
} else if (shown != null) {
if (from != null) {
leaving = true
delay(Motion.ms(PAGE_RETURN_MS).toLong())
}
shown = null
leaving = false
from = null
}
}
LaunchedEffect(shown) { detailScroll.scrollTo(0) }
val covering = shown != null && !leaving
val coveringNow = rememberUpdatedState(covering)
val gridScale by animateFloatAsState(if (covering) 0.95f else 1f, if (covering) Motion.tw(480) else Motion.tw(460, 40), label = "gridSink")
val gridAlpha by animateFloatAsState(if (covering) 0f else 1f, if (covering) Motion.tw(320, 80) else Motion.tw(300, 100), label = "gridFade")
val fade = with(density) { 14.dp.toPx() }
Box(
modifier.clipToBounds()
.onGloballyPositioned { viewportAt = it.positionInRoot() }
.graphicsLayer { compositingStrategy = androidx.compose.ui.graphics.CompositingStrategy.Offscreen }
.drawWithContent {
drawContent()
val scrolled = if (shown != null) detailScroll.value > 0 else gridScrolled()
if (scrolled && size.height > fade) drawRect(
Brush.verticalGradient(0f to Color.Transparent, fade / size.height to Color.Black),
blendMode = androidx.compose.ui.graphics.BlendMode.DstIn,
)
},
) {
CompositionLocalProvider(LocalFrontFocus provides if (covering) null else ff) {
Column(
modifier = Modifier.fillMaxSize()
.graphicsLayer { scaleX = gridScale; scaleY = gridScale; alpha = gridAlpha }
.focusProperties { enter = { if (coveringNow.value) FocusRequester.Cancel else FocusRequester.Default } }
.focusGroup(),
) { grid() }
}
shown?.let { key ->
wrap(key) {
val body: @Composable () -> Unit = {
Column(
Modifier.fillMaxSize().pointerInput(Unit) { detectTapGestures { } }
.verticalScroll(detailScroll).padding(top = 4.dp, bottom = 16.dp),
) {
Column(
Modifier.fillMaxWidth()
.onFocusChanged { onDetailFocus(it.hasFocus) }
.focusProperties { exit = { if (coveringNow.value && Steps.step == null && StoresMotion.step == null) FocusRequester.Cancel else FocusRequester.Default } }
.focusGroup(),
) { detail(key) }
}
}
val origin = from
if (origin == null) body()
else {
val painter = art?.let { url ->
coil.compose.rememberAsyncImagePainter(
remember(url) {
coil.request.ImageRequest.Builder(ctx).data(url).size(1280, 720)
.placeholderMemoryCacheKey(url).build()
},
)
}
PageFlood(origin, leaving, painter, onto = { StoresMotion.heroAt }, ontoCorner = 16.dp) { body() }
}
}
}
}
}
@@ -254,7 +254,7 @@ private fun Content(
return
}
if (selected == "games" || selected.startsWith("app:")) {
GamesPage(s, a, selected, onSelect, modifier)
LibraryPage(s, a, selected, onSelect, modifier)
return
}
if (selected == "desktop") {
@@ -5,7 +5,6 @@ import com.droiddeck.launcher.R
import androidx.compose.ui.res.stringResource
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
@@ -13,160 +12,39 @@ import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.key
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.ui.semantics.semantics
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import kotlinx.coroutines.flow.first
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.semantics.contentDescription
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 coil.compose.AsyncImage
import com.droiddeck.launcher.frontend.Library
import com.droiddeck.launcher.stores.epic.EpicClient
import kotlinx.coroutines.launch
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
@Composable
internal fun GamesPage(s: FrontEndState, a: FrontEndActions, selected: String, onSelect: (String) -> Unit, modifier: Modifier) {
val pageContext = androidx.compose.ui.platform.LocalContext.current
LaunchedEffect(Unit) { EpicClient.refresh(pageContext) }
val launcherName = stringResource(R.string.epic_client_title)
val epic = EpicClient.status
val launcherArt by androidx.compose.runtime.produceState<java.io.File?>(null, epic.version) {
value = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { EpicClient.icon(pageContext, epic.version) }
}
LaunchedEffect(Unit) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.load(pageContext) }
val cloudGames = com.droiddeck.launcher.stores.xbox.XboxCloudGames.entries.toList()
val games = remember(s.steamGames, s.shortcutPicker, epic, launcherName, launcherArt, cloudGames) {
val sorted = s.steamGames.sortedByDescending { it.lastPlayed }
if (s.shortcutPicker) sorted else {
val installedXbox = s.steamGames.filter { it.source == com.droiddeck.launcher.stores.Store.XBOX.id }.mapNotNull { it.storeId }.toSet()
EpicClient.withLauncher(sorted, EpicClient.launcherEntry(pageContext, launcherName, epic, launcherArt)) +
com.droiddeck.launcher.stores.xbox.XboxCloudGames.rows(pageContext, installedXbox)
}
}
val gameCount = games.count { it.library != Library.EPIC_LAUNCHER }
val narrow = LocalNarrowPane.current
val current = games.firstOrNull { "app:${it.appId}" == selected } ?: games.firstOrNull()
GamesDialogs(a, s.steamGames)
val onAdd: (() -> Unit)? = if (s.shortcutPicker) null else ({ GamesDialogState.adding = true })
val launch: (Library.SteamGame) -> Unit = { g ->
when (g.library) {
Library.EPIC_LAUNCHER -> a.onEpicLauncher()
Library.XBOX_CLOUD -> com.droiddeck.launcher.stores.xbox.XboxCloudGames.stream(pageContext, g.storeId.orEmpty(), g.name)
else -> a.onSteamGame(g)
}
}
val onEdit: (Library.SteamGame) -> Unit = { g ->
g.gameFiles?.let { GamesDialogState.editing = EditRequest(it.path, openTarget = false, fromSummary = false, preview = EditPreview.of(pageContext, g, it.path)) }
}
if (current == null || gameCount == 0) {
Column(modifier = modifier.padding(horizontal = if (narrow) 16.dp else 22.dp, vertical = if (narrow) 12.dp else 18.dp)) {
Rise(0) {
PageHeader(stringResource(R.string.content_games)) {
if (s.steamGamesX64) ClientTag()
onAdd?.let { AddGameButton(it) }
}
}
Rise(1) { Note(stringResource(if (s.shortcutPicker && s.shortcutLibraryScanning) R.string.game_shortcut_scanning else R.string.games_empty)) }
if (!s.shortcutPicker) Rise(2) {
Actions {
PrimaryButton(stringResource(R.string.games_play_steam), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow, modifier = Modifier.padding(top = 12.dp).testTag("play-steam"), onClick = a.onPlay)
games.firstOrNull { it.library == Library.EPIC_LAUNCHER }?.let { SecondaryButton(it.name, enabled = !s.busy, modifier = Modifier.padding(top = 12.dp)) { a.onEpicLauncher() } }
}
}
GameFileFolderActions(s, a)
}
return
}
val host = rememberMenuHost()
if (games.size == 1) {
Column(modifier = modifier.verticalScroll(rememberScrollState()).padding(horizontal = if (narrow) 16.dp else 22.dp, vertical = if (narrow) 12.dp else 18.dp)) {
if (onAdd != null) Rise(0) { GamesHeader(gameCount, s.steamGamesX64, onAdd, Modifier.padding(bottom = 10.dp)) }
Rise(0) {
Row(verticalAlignment = Alignment.Bottom) {
GameHero(current, Modifier.weight(1f).heightIn(min = if (narrow) 190.dp else 250.dp), corner = { GameRemoveButton(current, s, a) }) {
GameHeroCopy(current, if (narrow) 28.sp else 38.sp)
GameActions(current, s, a, onEdit, launch)
}
if (!narrow) Poster(current.art, current.name, Modifier.width(168.dp))
}
}
if (current.library != Library.XBOX_CLOUD) {
Rise(1) { SectionTitle(stringResource(R.string.games_launch_settings), null) }
Rise(2) { LaunchSettings(s, a, host, current) }
}
Rise(3) { GameFileFolderActions(s, a) }
}
return
}
val pal = LocalPalette.current
Row(modifier = modifier) {
GameList(games, gameCount, s.steamGamesX64, current, onSelect = { onSelect("app:${it.appId}") }, onLaunch = launch, onAdd = onAdd,
modifier = Modifier.width(if (narrow) 168.dp else 250.dp).fillMaxHeight())
Box(Modifier.width(1.dp).fillMaxHeight().background(pal.line))
Column(
modifier = Modifier.weight(1f).fillMaxHeight().verticalScroll(rememberScrollState())
.padding(horizontal = if (narrow) 14.dp else 20.dp, vertical = 16.dp),
) {
GameHero(current, Modifier.fillMaxWidth().heightIn(min = if (narrow) 170.dp else 200.dp), corner = { GameRemoveButton(current, s, a) }) {
GameHeroCopy(current, if (narrow) 24.sp else 32.sp)
GameActions(current, s, a, onEdit, launch)
}
if (current.library != Library.XBOX_CLOUD) {
SectionTitle(stringResource(R.string.games_launch_settings), null)
LaunchSettings(s, a, host, current)
}
GameFileFolderActions(s, a)
}
}
}
@Composable
private fun GameFileFolderActions(s: FrontEndState, a: FrontEndActions) {
internal fun GameFileFolderActions(s: FrontEndState, a: FrontEndActions) {
if (s.shortcutPicker) return
var open by remember { androidx.compose.runtime.mutableStateOf(false) }
Box(Modifier.padding(top = 12.dp)) {
Box {
SecondaryButton(stringResource(R.string.game_frontend_files), compact = true) { open = !open }
AnchoredMenu(open, onDismiss = { open = false }, title = s.gameSyncFolder ?: stringResource(R.string.game_frontend_files)) { first ->
MenuItem(stringResource(R.string.game_file_sync), checked = false, focusRequester = first) {
@@ -181,48 +59,7 @@ private fun GameFileFolderActions(s: FrontEndState, a: FrontEndActions) {
@Composable
private fun GameActions(g: Library.SteamGame, s: FrontEndState, a: FrontEndActions, onEdit: (Library.SteamGame) -> Unit, launch: (Library.SteamGame) -> Unit) {
val context = androidx.compose.ui.platform.LocalContext.current
if (g.library == Library.XBOX_CLOUD) {
androidx.compose.runtime.LaunchedEffect(g.gameId) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.prefetch(context) }
Actions {
PrimaryButton("Stream", enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow) { launch(g) }
SecondaryButton("Download") { com.droiddeck.launcher.stores.StoresState.openGameRequest = "${com.droiddeck.launcher.stores.Store.XBOX.id}:${g.storeId}" }
SecondaryButton("Remove from Games", compact = true) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.remove(context, g.storeId.orEmpty()) }
BusyChip(s)
}
return
}
val xboxStream by androidx.compose.runtime.produceState(false, g.gameId) {
value = g.source == com.droiddeck.launcher.stores.Store.XBOX.id && !g.storeId.isNullOrEmpty() && !s.shortcutPicker &&
kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.streams(context, g.storeId!!) }
}
androidx.compose.runtime.LaunchedEffect(xboxStream) { if (xboxStream) com.droiddeck.launcher.stores.xbox.XboxCloudGames.prefetch(context) }
Actions {
PrimaryButton(stringResource(if (s.shortcutPicker) R.string.game_shortcut_choose else R.string.games_launch), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow) { launch(g) }
if (xboxStream) SecondaryButton("Stream") { com.droiddeck.launcher.stores.xbox.XboxCloudGames.stream(context, g.storeId.orEmpty(), g.name) }
if (g.library == Library.EPIC_LAUNCHER) {
if (!s.shortcutPicker) UninstallEpicLauncher(s, a)
BusyChip(s)
return@Actions
}
g.gameFiles?.takeIf { it.isDirectory }?.let { dir ->
SecondaryButton(stringResource(R.string.games_files), compact = true) { a.onBrowseFiles(dir) }
}
g.protonPrefix?.takeIf { it.isDirectory }?.let { dir ->
SecondaryButton(stringResource(R.string.games_prefix), compact = true) { a.onBrowseFiles(dir) }
if (g.library == Library.ADDED) ManageSaves(g, dir, a)
}
if (!s.shortcutPicker) GameShortcutMenu(g, a)
if (!s.shortcutPicker && g.library == Library.ADDED && g.source == Library.ADDED && g.gameFiles != null) EditGameButton { onEdit(g) }
else if (!s.shortcutPicker && g.library != Library.ADDED && g.source == Library.SOURCE_STEAM) EditGameButton { GamesDialogState.steamArt = g }
else if (!s.shortcutPicker && g.library == Library.ADDED && g.source != Library.ADDED && g.gameFiles != null) EditGameButton { GamesDialogState.storeArt = g }
BusyChip(s)
}
}
@Composable
private fun UninstallEpicLauncher(s: FrontEndState, a: FrontEndActions) {
internal fun UninstallEpicLauncher(s: FrontEndState, a: FrontEndActions) {
var asking by remember { androidx.compose.runtime.mutableStateOf(false) }
SecondaryButton(stringResource(R.string.common_uninstall), enabled = !s.busy, compact = true) { asking = true }
if (asking) {
@@ -237,71 +74,6 @@ private fun UninstallEpicLauncher(s: FrontEndState, a: FrontEndActions) {
}
}
@Composable
private fun GameShortcutMenu(g: Library.SteamGame, a: FrontEndActions) {
var open by remember(g.gameId) { androidx.compose.runtime.mutableStateOf(false) }
Box {
SecondaryButton(stringResource(R.string.game_shortcut), compact = true) { open = !open }
AnchoredMenu(open, onDismiss = { open = false }, title = stringResource(R.string.game_shortcut)) { first ->
MenuItem(stringResource(R.string.game_shortcut_add), checked = false, focusRequester = first) {
open = false; a.onGameShortcut(g)
}
MenuItem(stringResource(R.string.game_file_export), checked = false) {
open = false; a.onExportGameFile(g)
}
MenuItem(stringResource(R.string.game_link_copy), checked = false) {
open = false; a.onCopyGameLink(g)
}
}
}
}
@Composable
private fun ManageSaves(g: Library.SteamGame, prefix: java.io.File, a: FrontEndActions) {
var open by remember(g.gameId) { androidx.compose.runtime.mutableStateOf(false) }
val saves by androidx.compose.runtime.produceState<List<com.droiddeck.launcher.session.GameSaves.SaveDir>?>(null, g.gameId, open) {
if (open) value = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
runCatching { com.droiddeck.launcher.session.GameSaves.locate(prefix, g) }.getOrDefault(emptyList())
}
}
val found = saves
val summary = when {
found == null -> stringResource(R.string.games_saves_looking)
found.isEmpty() -> stringResource(R.string.games_saves_none)
else -> {
val mb = found.sumOf { it.bytes } / 1048576.0
stringResource(
R.string.games_saves_summary,
if (found.size == 1) found[0].relPath else stringResource(R.string.games_saves_folders, found.size),
found.sumOf { it.files },
if (mb < 1) stringResource(R.string.games_kb, (mb * 1024).toInt()) else stringResource(R.string.common_size_mb, mb),
)
}
}
Box {
val cloudGame = com.droiddeck.launcher.stores.Store.byId(g.source).let { it == com.droiddeck.launcher.stores.Store.GOG || it == com.droiddeck.launcher.stores.Store.EPIC }
val label = stringResource(if (cloudGame) R.string.store_cloud_saves else R.string.games_manage_saves)
SecondaryButton(label, compact = true) { open = !open }
AnchoredMenu(open, onDismiss = { open = false }, title = if (cloudGame) label else stringResource(R.string.games_saves), note = summary) { first ->
if (cloudGame) CloudRows(g, open, first)
MenuItem(stringResource(R.string.games_import_saves), checked = false, focusRequester = if (cloudGame) null else first) {
open = false; a.onSaveImport(g)
}
MenuItem(stringResource(R.string.games_export_gamehub), checked = false) {
open = false; a.onSaveExport(g, com.droiddeck.launcher.session.GameSaves.Layout.GAMEHUB)
}
MenuItem(stringResource(R.string.games_export_winlator), checked = false) {
open = false; a.onSaveExport(g, com.droiddeck.launcher.session.GameSaves.Layout.WINLATOR)
}
found?.firstOrNull()?.let { d ->
MenuItem(stringResource(R.string.games_open_save_folder), checked = false, detail = d.relPath) {
open = false; a.onBrowseFiles(java.io.File(prefix, "drive_c/users/steamuser/" + d.relPath))
}
}
}
}
}
/** A skipped sync is not a success: "0 files" used to show for a signed-out store or cloud saves turned off. */
internal fun cloudMessage(context: android.content.Context, r: com.droiddeck.launcher.stores.CloudSaves.Result): String = when (r.result) {
"ok" -> when {
@@ -318,125 +90,7 @@ internal fun cloudMessage(context: android.content.Context, r: com.droiddeck.lau
}
@Composable
private fun CloudRows(g: Library.SteamGame, open: Boolean, first: androidx.compose.ui.focus.FocusRequester) {
val store = com.droiddeck.launcher.stores.Store.byId(g.source)?.takeIf { it != com.droiddeck.launcher.stores.Store.AMAZON } ?: return
val id = g.storeId ?: return
val context = androidx.compose.ui.platform.LocalContext.current.applicationContext
var busy by remember(g.gameId) { androidx.compose.runtime.mutableStateOf<String?>(null) }
var tick by remember(g.gameId) { androidx.compose.runtime.mutableStateOf(0) }
val status by androidx.compose.runtime.produceState<com.droiddeck.launcher.stores.CloudSaves.Status?>(null, g.gameId, open, tick) {
if (open) value = kotlinx.coroutines.withContext(kotlinx.coroutines.Dispatchers.IO) {
runCatching { com.droiddeck.launcher.stores.CloudSaves.status(context, store, id) }.getOrNull()
}
}
val folder = g.gameFiles
var enabled by remember(g.gameId, open) { androidx.compose.runtime.mutableStateOf(folder?.let { com.droiddeck.launcher.stores.StoreGameSidecar.read(it)?.cloud } ?: true) }
val st = status
val supported = st?.supported ?: true
val conflict = st != null && st.conflicts.isNotEmpty()
fun sync(up: Boolean, force: Boolean = false) {
if (busy != null) return
busy = if (up) "up" else "down"
Thread({
val r = if (up) com.droiddeck.launcher.stores.CloudSaves.upload(context, store, id, force) else com.droiddeck.launcher.stores.CloudSaves.download(context, store, id, force)
com.droiddeck.launcher.stores.StoresState.post { busy = null; tick++; android.widget.Toast.makeText(context, cloudMessage(context, r), android.widget.Toast.LENGTH_SHORT).show() }
}, "cloud-manual").start()
}
MenuItem(stringResource(R.string.store_cloud_saves), checked = enabled && supported, enabled = supported, focusRequester = first,
detail = if (!supported) stringResource(R.string.cloud_none) else null) {
val f = folder ?: return@MenuItem
val next = !enabled
Thread({
val written = runCatching { com.droiddeck.launcher.stores.StoreGameSidecar.updateCloud(f, next) }.getOrNull()
com.droiddeck.launcher.stores.StoresState.post { enabled = written?.cloud ?: enabled }
}, "cloud-switch").start()
}
val ready = st != null && supported && enabled && busy == null
val pending = stringResource(R.string.cloud_pending)
val summary = when {
st == null || busy != null -> pending
!supported -> stringResource(R.string.cloud_none)
conflict -> stringResource(R.string.cloud_conflict_files, st.conflicts.size)
st.lastSync > 0 -> stringResource(R.string.cloud_last_sync, android.text.format.DateUtils.getRelativeTimeSpanString(st.lastSync).toString())
else -> stringResource(R.string.cloud_never)
}
MenuItem(stringResource(if (conflict) R.string.cloud_keep_local else R.string.cloud_upload), checked = false, enabled = ready, detail = summary) { sync(up = true, force = conflict) }
MenuItem(stringResource(if (conflict) R.string.cloud_keep_cloud else R.string.cloud_download), checked = false, enabled = ready, detail = if (st == null || busy != null) pending else null) { sync(up = false, force = conflict) }
}
@Composable
private fun ColumnScope.GameHeroCopy(g: Library.SteamGame, titleSize: androidx.compose.ui.unit.TextUnit) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
SourceChip(g.source)
val eyebrow = lastPlayedText(g.lastPlayed) ?: libraryLabel(g)
if (eyebrow.isNotEmpty()) Text(
eyebrow.uppercase(),
fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = LocalPalette.current.signal,
maxLines = 1, overflow = TextOverflow.Ellipsis,
)
}
GameTitle(g, titleSize)
Spacer(Modifier.height(8.dp))
}
@Composable
private fun GameTitle(g: Library.SteamGame, titleSize: androidx.compose.ui.unit.TextUnit) {
val logo = g.logo
var failed by remember(logo) { androidx.compose.runtime.mutableStateOf(false) }
if (logo != null && !failed) {
AsyncImage(
model = logo, contentDescription = g.name, contentScale = ContentScale.Fit, alignment = Alignment.CenterStart,
onError = { failed = true },
modifier = Modifier.padding(vertical = 4.dp).height((titleSize.value * 2.2f).dp).widthIn(max = (titleSize.value * 9f).dp).testTag("game-logo"),
)
} else Text(
g.name, fontSize = titleSize, lineHeight = titleSize * 1.15f, fontWeight = FontWeight.Bold,
color = MaterialTheme.colorScheme.onBackground, maxLines = 2, overflow = TextOverflow.Ellipsis,
)
}
@Composable
private fun GameList(
games: List<Library.SteamGame>, count: Int, x64: Boolean, current: Library.SteamGame,
onSelect: (Library.SteamGame) -> Unit, onLaunch: (Library.SteamGame) -> Unit, onAdd: (() -> Unit)?, modifier: Modifier,
) {
val colors = MaterialTheme.colorScheme
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
modifier = modifier.verticalScroll(rememberScrollState()).padding(start = 12.dp, end = 10.dp, top = 16.dp, bottom = 16.dp),
) {
GamesHeader(count, x64, onAdd, Modifier.padding(start = 6.dp, bottom = 10.dp))
val seen = remember { HashSet<Int>() }
var settled by remember { androidx.compose.runtime.mutableStateOf(false) }
for (g in games) key(g.appId) {
Box(Modifier.arriveIn(settled && g.appId !in seen)) {
GameRow(g, g.appId == current.appId, onSelect = { onSelect(g) }, onLaunch = { onLaunch(g) })
}
}
androidx.compose.runtime.SideEffect {
seen.addAll(games.map { it.appId })
if (games.isNotEmpty()) settled = true
}
}
}
@Composable
private fun GamesHeader(count: Int, x64: Boolean, onAdd: (() -> Unit)?, modifier: Modifier) {
val colors = MaterialTheme.colorScheme
val narrow = LocalNarrowPane.current
Column(modifier) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
Text(stringResource(R.string.content_games), fontSize = 22.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1)
Text(count.toString(), fontSize = 13.sp, color = colors.onSurfaceVariant)
if (x64 && !narrow) ClientTag()
if (onAdd != null) AddGameButton(onAdd)
}
if (x64 && narrow) Box(Modifier.padding(top = 4.dp)) { ClientTag() }
}
}
@Composable
private fun ClientTag() {
internal fun ClientTag() {
Box(Modifier.testTag("games-client-x64")) { StateChip(stringResource(R.string.mode_client_arch_x64), ChipTone.PLAIN) }
}
@@ -451,7 +105,7 @@ internal object GamesDialogState {
}
@Composable
private fun GamesDialogs(a: FrontEndActions, games: List<Library.SteamGame>) {
internal fun GamesDialogs(a: FrontEndActions, games: List<Library.SteamGame>) {
val ctx = androidx.compose.ui.platform.LocalContext.current
val scope = androidx.compose.runtime.rememberCoroutineScope()
val st = GamesDialogState
@@ -505,68 +159,7 @@ private fun GamesDialogs(a: FrontEndActions, games: List<Library.SteamGame>) {
}
@Composable
private fun GameRemoveButton(g: Library.SteamGame, s: FrontEndState, a: FrontEndActions) {
val kind = com.droiddeck.launcher.frontend.GameRemoval.kind(g)
if (s.shortcutPicker || kind == com.droiddeck.launcher.frontend.GameRemoval.Kind.LAUNCHER) return
if (g.gameFiles?.isDirectory != true) return
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
var asking by remember(g.gameId) { androidx.compose.runtime.mutableStateOf(false) }
val shortcut = kind == com.droiddeck.launcher.frontend.GameRemoval.Kind.SHORTCUT
val label = stringResource(if (shortcut) R.string.edit_remove else R.string.common_uninstall)
val title = if (shortcut) stringResource(R.string.games_remove_title, g.name) else stringResource(R.string.stores_uninstall_title, g.name)
val body = when {
shortcut -> stringResource(R.string.games_remove_body)
g.library == Library.ADDED -> stringResource(R.string.stores_uninstall_body)
else -> stringResource(R.string.games_uninstall_body)
}
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.paneItem("game:remove").downToFirstTile().size(44.dp).clip(Shape12)
.background(if (hot) colors.error.copy(alpha = 0.22f) else Color.Black.copy(alpha = 0.34f))
.glideBorder(hot, Shape12, colors.error, pal.line2)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = androidx.compose.ui.semantics.Role.Button) { asking = true }
.controllerConfirm { asking = true }
.semantics { contentDescription = label },
) {
androidx.compose.material3.Icon(
Icons.Outlined.Delete, contentDescription = null,
tint = if (hot) colors.error else Color.White, modifier = Modifier.size(18.dp),
)
}
if (asking) ConfirmDialog(
title = title,
text = body,
confirm = label,
onConfirm = { a.onGameRemoved(g) },
onDismiss = { asking = false },
)
}
@Composable
private fun EditGameButton(onClick: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val label = stringResource(R.string.common_edit)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.paneItem("game:edit").downToFirstTile().size(44.dp).clip(Shape12)
.background(if (hot) pal.signal.copy(alpha = 0.14f) else Color.White.copy(alpha = 0.03f))
.glideBorder(hot, Shape12, pal.signal, pal.line2)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = androidx.compose.ui.semantics.Role.Button, onClick = onClick)
.controllerConfirm(onClick = onClick)
.semantics { contentDescription = label },
) {
androidx.compose.material3.Icon(Icons.Outlined.Edit, contentDescription = null, tint = colors.onBackground, modifier = Modifier.size(18.dp))
}
}
@Composable
private fun AddGameButton(onClick: () -> Unit) {
internal fun AddGameButton(onClick: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
@@ -586,48 +179,6 @@ private fun AddGameButton(onClick: () -> Unit) {
}
}
@Composable
internal fun Modifier.arriveIn(arriving: Boolean): Modifier {
val t = remember { androidx.compose.animation.core.Animatable(if (arriving) 0f else 1f) }
LaunchedEffect(Unit) { if (t.value < 1f) t.animateTo(1f, Motion.tw(280)) }
return graphicsLayer { alpha = t.value; translationY = (1f - t.value) * 12.dp.toPx() }
}
@Composable
private fun GameRow(g: Library.SteamGame, selected: Boolean, onSelect: () -> Unit, onLaunch: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val focused by src.collectIsFocusedAsState()
val hovered by src.collectIsHoveredAsState()
LaunchedEffect(focused) { if (focused && !selected) onSelect() }
Row(
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth().paneItem("game:${g.appId}")
.clip(Shape12)
.background(if (selected) pal.signal.copy(alpha = 0.14f) else if (hovered) Color.White.copy(alpha = 0.05f) else Color.Transparent)
.glideBorder(focused, Shape12, pal.signal)
.hoverable(src).clickable(role = androidx.compose.ui.semantics.Role.Button, interactionSource = src, indication = LocalIndication.current, onClick = onSelect)
.controllerConfirm(onClick = onLaunch)
.padding(horizontal = 10.dp, vertical = 6.dp),
) {
Box(Modifier.width(30.dp).height(45.dp).clip(RoundedCornerShape(5.dp)).background(artBrush(hueOf(g.name)))) {
if (g.art != null) AsyncImage(model = g.art, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize())
}
Column(modifier = Modifier.weight(1f)) {
Text(g.name, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
SourceChip(g.source, small = true)
Text(
if (g.library == Library.EPIC_LAUNCHER) libraryLabel(g)
else playedSpan(g.lastPlayed)?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.games_never_played),
fontSize = 12.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis,
)
}
}
}
}
@Composable
internal fun PageHeader(title: String, trailing: @Composable RowScope.() -> Unit = {}) {
Row(
@@ -656,11 +207,7 @@ internal fun RuntimeChip(s: FrontEndState) = when {
else -> Chip(stringResource(R.string.games_runtime_ready), ok = true)
}
@Composable
private fun lastPlayedText(lastPlayed: Long): String? =
playedSpan(lastPlayed)?.let { stringResource(R.string.games_last_played, it.replaceFirstChar { c -> c.lowercase() }) }
private fun playedSpan(lastPlayed: Long): String? {
internal fun playedSpan(lastPlayed: Long): String? {
if (lastPlayed <= 0L) return null
return android.text.format.DateUtils.getRelativeTimeSpanString(
lastPlayed * 1000L, System.currentTimeMillis(), android.text.format.DateUtils.MINUTE_IN_MILLIS,
@@ -668,38 +215,9 @@ private fun playedSpan(lastPlayed: Long): String? {
}
@Composable
private fun libraryLabel(g: Library.SteamGame): String = when {
g.library == Library.EPIC_LAUNCHER -> EpicClient.status.version?.substringBefore('+')?.let { stringResource(R.string.epic_client_version, it) } ?: ""
g.library == Library.XBOX_CLOUD -> "Xbox Cloud Gaming"
internal fun libraryLabel(g: Library.SteamGame): String = when {
g.library == Library.XBOX_CLOUD -> stringResource(R.string.xbox_cloud_gaming)
g.library != Library.ADDED -> if (g.library == "internal") stringResource(R.string.games_internal) else g.library
g.source != Library.ADDED -> stringResource(R.string.games_store_storage)
else -> ""
}
@Composable
private fun GameHero(g: Library.SteamGame, modifier: Modifier, corner: @Composable BoxScope.() -> Unit = {}, content: @Composable ColumnScope.() -> Unit) {
val colors = MaterialTheme.colorScheme
Box(modifier = modifier.clip(Shape16).background(artBrush(hueOf(g.name)))) {
val image = g.hero ?: g.art.takeIf { g.library != Library.EPIC_LAUNCHER }
if (image != null) AsyncImage(
model = image, contentDescription = null, contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize()
.then(if (g.hero == null) Modifier.blur(24.dp).graphicsLayer { scaleX = 1.3f; scaleY = 1.3f } else Modifier),
)
Spacer(
Modifier.matchParentSize().background(
Brush.horizontalGradient(
0f to colors.background.copy(alpha = 0.92f),
0.55f to colors.background.copy(alpha = 0.6f),
1f to Color.Transparent,
),
),
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp),
modifier = Modifier.align(Alignment.BottomStart).padding(horizontal = 22.dp, vertical = 18.dp),
content = content,
)
Box(Modifier.align(Alignment.TopEnd).padding(12.dp), content = corner)
}
}
@@ -141,7 +141,6 @@ class FrontEndState(
val launcherIcon: String = com.droiddeck.launcher.LauncherIcon.DARK,
val gameStoresEnabled: Boolean = true,
val gameStoresSpeedTier: String = "fast",
val storesOpenTab: String = SessionPrefs.STORES_OPEN_LIBRARY,
val navigate: String? = null,
val addedGamesArt: Boolean = true,
val sgdbUserKey: Boolean = false,
@@ -217,7 +216,6 @@ class FrontEndActions(
val onDownloadPrefix: (String) -> Unit = {},
val onGameStoresEnabled: (Boolean) -> Unit = {},
val onGameStoresSpeedTier: (String) -> Unit = {},
val onStoresOpenTab: (String) -> Unit = {},
val onNavigated: () -> Unit = {},
val onStoresShowMature: (Boolean) -> Unit = {},
val onStoresSort: (String) -> Unit = {},
@@ -440,7 +438,7 @@ private fun FrontEndScreenBody(s: FrontEndState, a: FrontEndActions, page: (@Com
}
LaunchedEffect(s.isHomeApp) { if (!s.isHomeApp && selected == "android-apps") selected = "steam" }
LaunchedEffect(s.steamGames.isEmpty()) {
if (s.steamGames.isEmpty() && selected.startsWith("app:")) selected = "games"
if (s.steamGames.isEmpty() && selected.startsWith("app:") && GameRef.parse(selected).appId != null) selected = "games"
}
remember { Motion.refresh(ctx); true }
@@ -553,6 +551,8 @@ private fun FrontEndScreenBody(s: FrontEndState, a: FrontEndActions, page: (@Com
}
FlightsLayer(screenAt)
GamesDialogs(a, s.steamGames)
com.droiddeck.launcher.stores.StoresState.pendingInstall?.let { item -> StoreTheme(item.store) { InstallWhere(item) } }
StepsLayer(screenAt)
appToChooseDisplay?.let { app ->
@@ -508,7 +508,7 @@ private fun FrameGenMenu(s: FrontEndState, a: FrontEndActions, host: MenuHost) {
}
@Composable
internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost, game: com.droiddeck.launcher.frontend.Library.SteamGame? = null) {
internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost, game: com.droiddeck.launcher.frontend.Library.SteamGame? = null, hopRail: String = "games") {
val columns = if (LocalNarrowPane.current) 2 else 3
val controller = a.controller
val context = androidx.compose.ui.platform.LocalContext.current
@@ -527,7 +527,7 @@ internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost
val at = Hops.sources["card:components"]
val open = { if (game == null) a.onComponents(true) else a.onGameComponents(game) }
if (at == null) open()
else Hops.go(HopOrigin("games", HOP_SURFACE_GAMES, null, "card:components", at, back, dest = "components", page = "components"), open)
else Hops.go(HopOrigin(hopRail, HOP_SURFACE_GAMES, null, "card:components", at, back, dest = "components", page = "components"), open)
}
}
add { m ->
@@ -570,18 +570,11 @@ internal fun LaunchSettings(s: FrontEndState, a: FrontEndActions, host: MenuHost
}
}
@Composable
private fun GameSettingsCard(host: MenuHost, game: com.droiddeck.launcher.frontend.Library.SteamGame, scope: String) {
val appContext = androidx.compose.ui.platform.LocalContext.current.applicationContext
val colors = MaterialTheme.colorScheme
var config by remember(scope) { mutableStateOf(runCatching { com.droiddeck.launcher.session.GameEnvironmentStore.read(appContext) }.getOrNull()) }
var saving by remember(scope) { mutableStateOf(false) }
val current = config
val noSteam = current != null && com.droiddeck.launcher.core.GameEnvironment.gameNoSteam(current, scope)
val fex = current?.let { runCatching { com.droiddeck.launcher.core.GameEnvironment.gameFexPreset(it, scope) }.getOrNull() }
val sync = current?.let { runCatching { com.droiddeck.launcher.core.GameEnvironment.gameSync(it, scope) }.getOrNull() }
val ready = current != null && !saving
val launcher = game.library == com.droiddeck.launcher.frontend.Library.EPIC_LAUNCHER
internal class GameSettingsState(private val appContext: android.content.Context, val scope: String) {
var config by mutableStateOf(runCatching { com.droiddeck.launcher.session.GameEnvironmentStore.read(appContext) }.getOrNull())
var saving by mutableStateOf(false)
val noSteam: Boolean get() = config?.let { com.droiddeck.launcher.core.GameEnvironment.gameNoSteam(it, scope) } == true
fun save(change: (android.content.Context) -> com.droiddeck.launcher.core.GameEnvironment.Config?) {
if (saving) return
saving = true
@@ -595,7 +588,37 @@ private fun GameSettingsCard(host: MenuHost, game: com.droiddeck.launcher.fronte
}
}, "game-settings").start()
}
val key = "game-settings"
}
@Composable
internal fun rememberGameSettings(scope: String): GameSettingsState {
val appContext = androidx.compose.ui.platform.LocalContext.current.applicationContext
return remember(scope) { GameSettingsState(appContext, scope) }
}
@Composable
private fun GameSettingsCard(host: MenuHost, game: com.droiddeck.launcher.frontend.Library.SteamGame, scope: String) {
val state = rememberGameSettings(scope)
SettingCard(stringResource(R.string.game_settings_card), stringResource(if (state.noSteam) R.string.game_steam_without else R.string.game_steam_with), "card:$GAME_SETTINGS_MENU", Modifier.fillMaxSize()) {
host.open = if (host.open == GAME_SETTINGS_MENU) null else GAME_SETTINGS_MENU
}
GameSettingsMenus(host, game, state)
}
/** The per-game settings menus, opened by setting [MenuHost.open] to [GAME_SETTINGS_MENU]. */
@Composable
internal fun GameSettingsMenus(host: MenuHost, game: com.droiddeck.launcher.frontend.Library.SteamGame, state: GameSettingsState) {
val appContext = androidx.compose.ui.platform.LocalContext.current.applicationContext
val colors = MaterialTheme.colorScheme
val scope = state.scope
val current = state.config
val noSteam = state.noSteam
val fex = current?.let { runCatching { com.droiddeck.launcher.core.GameEnvironment.gameFexPreset(it, scope) }.getOrNull() }
val sync = current?.let { runCatching { com.droiddeck.launcher.core.GameEnvironment.gameSync(it, scope) }.getOrNull() }
val ready = current != null && !state.saving
val launcher = game.library == com.droiddeck.launcher.frontend.Library.EPIC_LAUNCHER
fun save(change: (android.content.Context) -> com.droiddeck.launcher.core.GameEnvironment.Config?) = state.save(change)
val key = GAME_SETTINGS_MENU
val fexKey = "game-settings-fex"
val syncKey = "game-settings-sync"
val syncLabels = mapOf(
@@ -609,9 +632,6 @@ private fun GameSettingsCard(host: MenuHost, game: com.droiddeck.launcher.fronte
val custom = stringResource(R.string.comp_game_fex_custom)
val customSync = stringResource(R.string.game_sync_custom)
val resetDone = stringResource(R.string.comp_game_reset_done, game.name)
SettingCard(stringResource(R.string.game_settings_card), stringResource(if (noSteam) R.string.game_steam_without else R.string.game_steam_with), "card:$key", Modifier.fillMaxSize()) {
host.open = if (host.open == key) null else key
}
AnchoredMenu(host.open == key, onDismiss = { if (host.open == key) host.open = null }, title = stringResource(R.string.game_settings_menu_title, game.name)) { first ->
MenuItem(stringResource(R.string.game_no_steam_title), checked = noSteam, enabled = ready,
detail = stringResource(R.string.game_no_steam_detail), focusRequester = first) {
@@ -686,6 +706,8 @@ private fun GameSettingsCard(host: MenuHost, game: com.droiddeck.launcher.fronte
}
}
internal const val GAME_SETTINGS_MENU = "game-settings"
private const val EPIC_RESOLVE_URL = "https://www.epicgames.com/id/login?redirectUrl=https%3A%2F%2Fwww.epicgames.com%2Faccount%2Fpersonal"
@Composable
@@ -0,0 +1,830 @@
package com.droiddeck.launcher.ui
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusable
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
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.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material.icons.outlined.Edit
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.blur
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
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.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.droiddeck.launcher.R
import com.droiddeck.launcher.frontend.GameRemoval
import com.droiddeck.launcher.frontend.Library
import com.droiddeck.launcher.session.GameSaves
import com.droiddeck.launcher.stores.CatalogItem
import com.droiddeck.launcher.stores.CloudSaves
import com.droiddeck.launcher.stores.InstalledStoreGame
import com.droiddeck.launcher.stores.Lookup
import com.droiddeck.launcher.stores.Store
import com.droiddeck.launcher.stores.StoreAchievements
import com.droiddeck.launcher.stores.StoreGameSidecar
import com.droiddeck.launcher.stores.StoreInstallRoot
import com.droiddeck.launcher.stores.StoreLanguage
import com.droiddeck.launcher.stores.StoreSizes
import com.droiddeck.launcher.stores.StoresState
import com.droiddeck.launcher.stores.download.DownloadQueue
import com.droiddeck.launcher.stores.download.DownloadState
import com.droiddeck.launcher.stores.formatBytes
import com.droiddeck.launcher.stores.xbox.XboxBackend
import com.droiddeck.launcher.stores.xbox.XboxCloudGames
import com.droiddeck.launcher.stores.xbox.XboxContent
import com.droiddeck.launcher.stores.xbox.xboxCloud
import com.droiddeck.launcher.stores.xbox.xboxPc
import com.droiddeck.launcher.stores.xbox.XboxRuntime
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import androidx.compose.runtime.setValue
import androidx.compose.runtime.getValue
/** What a game page shows: `app:<appId>` for a game in the library, `[app:]<store>:<id>` for a store product. */
internal class GameRef(val appId: Int?, val store: Store?, val storeId: String?) {
fun game(games: List<Library.SteamGame>): Library.SteamGame? = when {
appId != null -> games.firstOrNull { it.appId == appId }
store != null && storeId != null -> games.firstOrNull { it.source == store.id && it.storeId == storeId }
else -> null
}
companion object {
fun parse(key: String): GameRef {
val rest = key.removePrefix("app:")
rest.toIntOrNull()?.let { return GameRef(it, null, null) }
val store = Store.byId(rest.substringBefore(':'))
return GameRef(null, store, rest.substringAfter(':', "").takeIf { store != null && it.isNotEmpty() })
}
fun key(game: Library.SteamGame) = "app:${game.appId}"
fun key(item: CatalogItem) = "app:${item.key}"
}
}
internal enum class GameTab { ABOUT, ACHIEVEMENTS, LAUNCH, SAVES, INSTALL }
@Composable
internal fun GameDetail(
key: String, s: FrontEndState, a: FrontEndActions, lead: FocusRequester, backLabel: String, hopRail: String?, onBack: () -> Unit,
) {
val narrow = LocalNarrowPane.current
val stacked = narrow || LocalConfiguration.current.screenWidthDp < 700
val ref = remember(key) { GameRef.parse(key) }
val games = s.steamGames + rememberXboxCloudRows(s)
val found = ref.game(games)
val known = remember(key) { arrayOfNulls<Pair<Store, String>>(1) }
val store = ref.store ?: Store.byId(found?.source) ?: known[0]?.first
val id = ref.storeId ?: found?.storeId ?: known[0]?.second
SideEffect { if (store != null && id != null) known[0] = store to id }
val game = found ?: if (store != null && id != null) s.steamGames.firstOrNull { it.source == store.id && it.storeId == id } else null
val item = if (store != null && id != null) StoresState.detailItem(store, id) else null
val installed = if (store != null && id != null) StoresState.installedGame(store, id) else null
val rawDownload = if (store != null && id != null) StoresState.download("${store.id}:$id") else null
val download = rawDownload?.takeIf { it.isActive }
val failed = rawDownload?.takeIf { it.state == DownloadState.FAILED }
val nothing = game == null && store == null
LaunchedEffect(nothing) { if (nothing) onBack() }
if (nothing) return
val source = game?.source ?: store?.id ?: Library.SOURCE_STEAM
val title = game?.name ?: item?.title ?: installed?.sidecar?.title ?: id.orEmpty()
val scores = if (source != Library.ADDED) rememberGameScores(title.takeIf { it.isNotBlank() && it != id }) else null
val achievements = remember(store, id, item?.accessible, installed) {
when {
store == null || StoreAchievements.forSource(store.id) == null -> null
item?.accessible == true -> StoreAchievements.Game.of(item)
installed != null -> StoreAchievements.Game.of(installed.sidecar)
else -> null
}
}
val achievementState = rememberAchievements(achievements)
val listed = if (store == null) null else (StoresState.found[store].orEmpty() + StoresState.shelves[store]?.all.orEmpty()).firstOrNull { it.id == id }
val developer = item?.developer?.ifBlank { null } ?: listed?.developer.orEmpty()
val publisher = item?.publisher?.ifBlank { null } ?: listed?.publisher.orEmpty()
val releaseDate = item?.releaseDate?.ifBlank { null } ?: listed?.releaseDate.orEmpty()
val description = item?.let { com.droiddeck.launcher.stores.cleanStoreText(it.description) }.orEmpty()
.takeUnless { it.trim().equals(title.trim(), ignoreCase = true) }.orEmpty()
val scored = scores != null && (scores.metacritic != Lookup.None || scores.hltb != Lookup.None)
val prefix = game?.protonPrefix?.takeIf { it.isDirectory }
val tabs = buildList {
if (description.isNotBlank() || developer.isNotBlank() || publisher.isNotBlank() || releaseDate.isNotBlank() || (scored && stacked)) add(GameTab.ABOUT)
if (achievementState != null) add(GameTab.ACHIEVEMENTS)
val cloud = game?.library == Library.XBOX_CLOUD
if (game != null && !cloud) add(GameTab.LAUNCH)
if (game != null && game.library == Library.ADDED && prefix != null) add(GameTab.SAVES)
if ((game != null && !cloud) || installed != null) add(GameTab.INSTALL)
}
var picked by remember(key) { mutableStateOf<GameTab?>(null) }
val tab = picked?.takeIf { it in tabs } ?: tabs.firstOrNull()
val tabFocus = remember(key) { GameTab.entries.associateWith { FocusRequester() } }
var inPanel by remember(key) { mutableStateOf(false) }
var inPage by remember(key) { mutableStateOf(false) }
var onStrip by remember(key) { mutableStateOf(false) }
var dropped by remember(key) { mutableStateOf(0) }
// A tab's content is swapped or refreshed under the focused row; focus goes back to the tabs, not to the rail.
LaunchedEffect(dropped) {
if (dropped == 0) return@LaunchedEffect
androidx.compose.runtime.withFrameNanos { }
val t = tab ?: return@LaunchedEffect
if (!inPage) focusWithinFrames({ onStrip }) { tabFocus.getValue(t) }
}
fun cycle(step: Int) {
if (tabs.size < 2) return
val at = tabs.indexOf(tab).coerceAtLeast(0)
picked = tabs[(at + step + tabs.size) % tabs.size]
}
Column(Modifier.fillMaxWidth().onFocusChanged { inPage = it.hasFocus }.bumpers(onPrevious = { cycle(-1) }, onNext = { cycle(1) })) {
Rise(0) {
Box(Modifier.padding(bottom = 12.dp)) { BackLink(backLabel, compact = true, onClick = onBack) }
}
val hero: @Composable () -> Unit = {
DisposableEffect(Unit) { onDispose { StoresMotion.heroAt = null } }
GameHero(
title, item, installed, game,
Modifier.fillMaxWidth().heightIn(min = if (narrow) 170.dp else 210.dp).onGloballyPositioned { StoresMotion.heroAt = it.boundsInRoot() },
corner = { if (!s.shortcutPicker) RemoveButton(title, game, installed, a) },
scores = scores.takeIf { !stacked },
) {
HeroEyebrow(source, game, item, installed, download?.location ?: failed?.location)
GameTitle(title, game?.logo ?: item?.logoUrl, if (narrow) 26.sp else 34.sp)
if (!failed?.error.isNullOrBlank()) Text(
failed!!.error!!, fontSize = 13.sp, lineHeight = 17.sp, fontWeight = FontWeight.Medium,
color = MaterialTheme.colorScheme.error, maxLines = 3, overflow = TextOverflow.Ellipsis,
)
Spacer(Modifier.height(4.dp))
GameActions(store, id, game, item, installed, s, a, Modifier.focusRequester(lead))
}
}
if (StoresMotion.heroLands) hero() else Rise(1) { hero() }
if (tab != null) Rise(2) {
Column(Modifier.fillMaxWidth().padding(top = 14.dp), verticalArrangement = Arrangement.spacedBy(12.dp)) {
val labels = tabs.map { t ->
when (t) {
GameTab.ABOUT -> stringResource(R.string.game_tab_about)
GameTab.ACHIEVEMENTS -> achievementState?.list?.takeIf { it.isNotEmpty() }?.let { list ->
stringResource(R.string.game_tab_achievements_count, stringResource(R.string.achievements_title), achievementState.done, list.size)
} ?: stringResource(R.string.achievements_title)
GameTab.LAUNCH -> stringResource(R.string.game_tab_launch)
GameTab.SAVES -> stringResource(if (Store.byId(source).let { it == Store.GOG || it == Store.EPIC }) R.string.game_tab_saves_cloud else R.string.games_saves)
GameTab.INSTALL -> stringResource(R.string.game_tab_install)
}
}
TabStrip(
labels, tabs.indexOf(tab), { picked = tabs[it] }, Modifier.onFocusChanged { onStrip = it.hasFocus },
focusRequesters = tabs.map { tabFocus.getValue(it) }, bumpers = tabs.size > 1, compact = narrow,
)
Box(Modifier.fillMaxWidth().onFocusChanged { if (inPanel && !it.hasFocus) dropped++; inPanel = it.hasFocus }.testTag("game-tab-${tab.name.lowercase()}")) {
when (tab) {
GameTab.ABOUT -> AboutTab(developer, publisher, releaseDate, description, scores, stacked)
GameTab.ACHIEVEMENTS -> achievementState?.let { st -> AchievementsTab(StoreAchievements.forSource(store?.id)?.label.orEmpty(), st) }
GameTab.LAUNCH -> if (game != null) LaunchTab(game, s, a, hopRail)
GameTab.SAVES -> if (game != null && prefix != null) SavesTab(game, prefix, a)
GameTab.INSTALL -> InstallTab(game, installed, item, store)
}
}
}
}
}
}
@Composable
private fun GameHero(
title: String, item: CatalogItem?, installed: InstalledStoreGame?, game: Library.SteamGame?, modifier: Modifier,
corner: @Composable BoxScope.() -> Unit, scores: GameScores?, content: @Composable ColumnScope.() -> Unit,
) {
val colors = MaterialTheme.colorScheme
Box(modifier.clip(Shape16).background(artBrush(hueOf(title)))) {
val wide: Any? = game?.hero ?: item?.imageUrl ?: installed?.sidecar?.hero ?: installed?.sidecar?.cover
if (wide != null) {
// The card it came from keeps its own bitmap under the hero until the wide art is decoded.
val request = if (wide is String) cardArtRequest(wide, item?.tallImageUrl) else wide
AsyncImage(model = request, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize())
} else game?.art?.let { art ->
AsyncImage(
model = art, contentDescription = null, contentScale = ContentScale.Crop,
modifier = Modifier.matchParentSize().blur(24.dp).graphicsLayer { scaleX = 1.3f; scaleY = 1.3f },
)
}
Spacer(Modifier.matchParentSize().background(Brush.horizontalGradient(0f to colors.background.copy(alpha = 0.94f), 0.55f to colors.background.copy(alpha = 0.58f), 1f to Color.Transparent)))
if (scores != null) {
Spacer(Modifier.matchParentSize().background(Brush.horizontalGradient(0.45f to Color.Transparent, 1f to colors.background.copy(alpha = 0.75f))))
HeroScores(scores, Modifier.align(Alignment.BottomEnd).padding(end = 16.dp, bottom = 18.dp))
}
Column(
verticalArrangement = Arrangement.spacedBy(6.dp),
modifier = Modifier.align(Alignment.BottomStart).padding(horizontal = 22.dp, vertical = 18.dp),
content = content,
)
Box(Modifier.align(Alignment.TopEnd).padding(12.dp), content = corner)
}
}
@Composable
private fun HeroEyebrow(source: String, game: Library.SteamGame?, item: CatalogItem?, installed: InstalledStoreGame?, downloadAt: String?) {
val ctx = LocalContext.current
val pal = LocalPalette.current
val folder = installed?.folder ?: game?.gameFiles
val passName = item?.includedWith.orEmpty()
val state = when {
installed != null -> listOf(stringResource(R.string.stores_eyebrow_installed), StoreInstallRoot.labelFor(ctx, installed.folder))
game?.library == Library.XBOX_CLOUD -> listOf(libraryLabel(game))
game != null -> listOf(stringResource(R.string.stores_eyebrow_installed), libraryLabel(game))
!downloadAt.isNullOrBlank() -> listOf(stringResource(R.string.stores_eyebrow_library), downloadAt)
item?.owned == true -> listOf(stringResource(R.string.stores_eyebrow_library))
item?.subscription == true -> listOf(passName.ifBlank { stringResource(R.string.stores_game_pass) })
passName.isNotBlank() -> listOf(stringResource(R.string.stores_eyebrow_pass, passName))
item?.accessible == true -> listOf(stringResource(R.string.stores_eyebrow_library))
else -> listOf(stringResource(R.string.stores_eyebrow_store))
}
val size = if (folder != null && (installed != null || game != null)) diskSize(installed, folder) else (item?.sizeBytes ?: 0L).takeIf { it > 0 }
val line = (state + listOfNotNull(size?.let { formatBytes(it) })).filter { it.isNotBlank() }.joinToString(" · ")
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
SourceChip(source)
Text(line.uppercase(), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = pal.signal, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
@Composable
private fun GameTitle(title: String, logo: Any?, titleSize: TextUnit) {
var failed by remember(logo) { mutableStateOf(false) }
if (logo != null && !failed) AsyncImage(
model = logo, contentDescription = title, contentScale = ContentScale.Fit, alignment = Alignment.CenterStart,
onError = { failed = true },
modifier = Modifier.padding(vertical = 4.dp).height((titleSize.value * 2.2f).dp).widthIn(max = (titleSize.value * 9f).dp).testTag("game-logo"),
) else Text(
title, fontSize = titleSize, lineHeight = titleSize * 1.12f, fontWeight = FontWeight.Black,
color = MaterialTheme.colorScheme.onBackground, maxLines = 2, overflow = TextOverflow.Ellipsis,
)
}
@Composable
private fun GameActions(
store: Store?, id: String?, game: Library.SteamGame?, item: CatalogItem?, installed: InstalledStoreGame?,
s: FrontEndState, a: FrontEndActions, leadOn: Modifier,
) {
val ctx = LocalContext.current
val storeKey = if (store != null && id != null) "${store.id}:$id" else null
val rawDownload = storeKey?.let { StoresState.download(it) }
val download = rawDownload?.takeIf { it.isActive }
val failed = rawDownload?.takeIf { it.state == DownloadState.FAILED }
Actions {
when {
s.shortcutPicker && game != null -> {
PrimaryButton(stringResource(R.string.game_shortcut_choose), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow, modifier = leadOn) { a.onSteamGame(game) }
FileButtons(game, a)
}
game?.library == Library.XBOX_CLOUD -> {
val cloudId = game.storeId.orEmpty()
XboxStreamButton(cloudId, game.name, main = true, modifier = leadOn)
if (download != null) ActionChip(downloadLabel(download), ok = false)
else if (item?.installable == true) SecondaryButton(
if (item.sizeBytes > 0) stringResource(R.string.stores_install_size, formatBytes(item.sizeBytes)) else stringResource(R.string.stores_install),
) { StoresState.requestInstall(ctx, item) }
XboxLibraryButton(cloudId, item)
}
game != null || installed != null -> {
val gone = storeKey?.let { StoresState.uninstalling[it] }
val xboxBusy = store == Store.XBOX && id != null && XboxContent.busy(id)
PrimaryButton(
stringResource(R.string.stores_play), enabled = !s.busy && gone == null && download == null && !xboxBusy,
main = true, icon = Icons.Filled.PlayArrow, modifier = leadOn,
) {
if (game != null) a.onSteamGame(game) else if (store != null && id != null) launchStoreGame(ctx, store, id, s, a)
}
if ((game?.source ?: store?.id) != Library.SOURCE_STEAM) ActionChip(stringResource(R.string.stores_in_steam), ok = true)
if (game != null) {
FileButtons(game, a)
GameShortcutMenu(game, a)
EditArtButton(game)
}
if (store == Store.XBOX && id != null) XboxInstalledStream(id, game?.name ?: item?.title.orEmpty(), item)
if (store == Store.XBOX && installed != null && id != null) {
SecondaryButton(stringResource(R.string.stores_xbox_update), enabled = download == null && !XboxRuntime.isActive(id)) {
runCatching { XboxBackend.update(ctx, installed) }
.onFailure { StoresState.problems[Store.XBOX] = it.message ?: ctx.getString(R.string.stores_xbox_close_first) }
}
SecondaryButton(stringResource(R.string.stores_xbox_manage), enabled = download == null) { XboxContent.manage(ctx, installed) }
}
}
download != null -> {
ActionChip(downloadLabel(download), ok = false)
if (download.state != DownloadState.PAUSED) SecondaryButton(stringResource(R.string.stores_dl_pause), modifier = leadOn) { DownloadQueue.pause(download.key) }
else PrimaryButton(stringResource(R.string.stores_dl_resume), main = true, modifier = leadOn) { DownloadQueue.resume(ctx, download.key) }
ConfirmButton(stringResource(R.string.stores_dl_cancel), stringResource(R.string.stores_dl_cancel_confirm)) { DownloadQueue.cancel(ctx, download.key) }
}
failed != null -> {
ActionChip(stringResource(R.string.stores_dl_failed), ok = false)
PrimaryButton(stringResource(R.string.stores_dl_resume), main = true, modifier = leadOn) {
if (item != null) StoresState.requestInstall(ctx, item) else DownloadQueue.retry(ctx, failed.key)
}
ConfirmButton(stringResource(R.string.stores_dl_clear), stringResource(R.string.stores_dl_cancel_confirm), compact = true) { DownloadQueue.clear(ctx, failed.key) }
}
store == Store.XBOX && item != null && !item.xboxPc && item.xboxCloud -> {
XboxStreamButton(item.id, item.title, main = true, modifier = leadOn)
XboxLibraryButton(item.id, item)
}
item?.installable == true && StoresState.isUnfinished(item) -> {
InstallButton(stringResource(R.string.stores_resume_install), leadOn) { StoresState.requestInstall(ctx, item) }
ConfirmButton(stringResource(R.string.stores_dl_clear), stringResource(R.string.stores_dl_cancel_confirm), compact = true) { StoresState.clearUnfinished(ctx, item) }
}
item?.installable == true -> {
InstallButton(
if (item.sizeBytes > 0) stringResource(R.string.stores_install_size, formatBytes(item.sizeBytes)) else stringResource(R.string.stores_install),
leadOn,
) { StoresState.requestInstall(ctx, item) }
if (store == Store.XBOX && item.xboxCloud) {
XboxStreamButton(item.id, item.title)
XboxLibraryButton(item.id, item)
}
if (!item.accessible) SecondaryButton(buyLabel(item)) { openStoreUrl(ctx, item) }
}
item != null && item.isFree -> PrimaryButton(stringResource(R.string.stores_get_free), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
item != null -> PrimaryButton(buyLabel(item), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
}
BusyChip(s)
}
}
/** Xbox Cloud Gaming rows the user added to the Library, for games not installed here. */
@Composable
internal fun rememberXboxCloudRows(s: FrontEndState): List<Library.SteamGame> {
val ctx = LocalContext.current
LaunchedEffect(Unit) { XboxCloudGames.load(ctx) }
if (s.shortcutPicker) return emptyList()
val added = XboxCloudGames.entries.toList()
return remember(added, s.steamGames) {
val installedXbox = s.steamGames.filter { it.source == Store.XBOX.id }.mapNotNull { it.storeId }.toSet()
XboxCloudGames.rows(ctx, installedXbox)
}
}
@Composable
private fun XboxStreamButton(id: String, title: String, main: Boolean = false, modifier: Modifier = Modifier) {
val ctx = LocalContext.current
LaunchedEffect(Unit) { XboxCloudGames.prefetch(ctx) }
val label = stringResource(R.string.game_stream)
if (main) PrimaryButton(label, main = true, icon = Icons.Filled.PlayArrow, modifier = modifier) { XboxCloudGames.stream(ctx, id, title) }
else SecondaryButton(label, modifier = modifier) { XboxCloudGames.stream(ctx, id, title) }
}
@Composable
private fun XboxInstalledStream(id: String, title: String, item: CatalogItem?) {
val ctx = LocalContext.current
val streams by produceState(item?.xboxCloud == true, id) {
if (!value) value = withContext(Dispatchers.IO) { XboxCloudGames.streams(ctx, id) }
}
if (streams) XboxStreamButton(id, title)
}
@Composable
private fun XboxLibraryButton(id: String, item: CatalogItem?) {
val ctx = LocalContext.current
LaunchedEffect(Unit) { XboxCloudGames.load(ctx) }
// Add and Remove replace each other; the controller focus moves to the new button instead of getting lost.
val slot = remember { FocusRequester() }
val inputMode = androidx.compose.ui.platform.LocalInputModeManager.current
val scope = rememberCoroutineScope()
fun refocus() { if (inputMode.inputMode == androidx.compose.ui.input.InputMode.Keyboard) scope.launch { focusSoon { slot } } }
if (XboxCloudGames.isAdded(id)) {
ActionChip(stringResource(R.string.library_in), ok = true)
SecondaryButton(stringResource(R.string.library_remove), compact = true, modifier = Modifier.focusRequester(slot)) { XboxCloudGames.remove(ctx, id); refocus() }
} else if (item != null) SecondaryButton(stringResource(R.string.library_add), modifier = Modifier.focusRequester(slot)) { XboxCloudGames.add(ctx, item); refocus() }
}
@Composable
private fun buyLabel(item: CatalogItem): String = when {
item.isFree -> stringResource(R.string.stores_get_free)
item.hasPrice && item.finalPrice.isNotBlank() -> stringResource(R.string.stores_buy_price, item.finalPrice)
else -> stringResource(R.string.stores_view_on, item.store.shortLabel)
}
@Composable
private fun FileButtons(game: Library.SteamGame, a: FrontEndActions) {
game.gameFiles?.takeIf { it.isDirectory }?.let { dir -> SecondaryButton(stringResource(R.string.games_files), compact = true) { a.onBrowseFiles(dir) } }
game.protonPrefix?.takeIf { it.isDirectory }?.let { dir -> SecondaryButton(stringResource(R.string.games_prefix), compact = true) { a.onBrowseFiles(dir) } }
}
@Composable
private fun GameShortcutMenu(g: Library.SteamGame, a: FrontEndActions) {
var open by remember(g.gameId) { mutableStateOf(false) }
Box {
SecondaryButton(stringResource(R.string.game_shortcut), compact = true) { open = !open }
AnchoredMenu(open, onDismiss = { open = false }, title = stringResource(R.string.game_shortcut)) { first ->
MenuItem(stringResource(R.string.game_shortcut_add), checked = false, focusRequester = first) { open = false; a.onGameShortcut(g) }
MenuItem(stringResource(R.string.game_file_export), checked = false) { open = false; a.onExportGameFile(g) }
MenuItem(stringResource(R.string.game_link_copy), checked = false) { open = false; a.onCopyGameLink(g) }
}
}
}
/** The pencil opens the editor each kind of game always had: the shortcut editor for added games, art for the rest. */
@Composable
private fun EditArtButton(g: Library.SteamGame) {
val ctx = LocalContext.current
val folder = g.gameFiles
val open: (() -> Unit)? = when {
g.library == Library.ADDED && g.source == Library.ADDED && folder != null -> {
{ GamesDialogState.editing = EditRequest(folder.path, openTarget = false, fromSummary = false, preview = EditPreview.of(ctx, g, folder.path)) }
}
g.library != Library.ADDED && g.source == Library.SOURCE_STEAM -> { { GamesDialogState.steamArt = g } }
g.library == Library.ADDED && g.source != Library.ADDED && folder != null -> { { GamesDialogState.storeArt = g } }
else -> null
}
if (open != null) IconAction(Icons.Outlined.Edit, stringResource(R.string.common_edit), "game:edit", onClick = open)
}
@Composable
private fun IconAction(icon: androidx.compose.ui.graphics.vector.ImageVector, label: String, id: String, danger: Boolean = false, enabled: Boolean = true, onClick: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src) && enabled
val accent = if (danger) colors.error else pal.signal
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.paneItem(id).downToFirstTile().size(44.dp).clip(Shape12)
.background(if (hot) accent.copy(alpha = if (danger) 0.22f else 0.14f) else if (danger) Color.Black.copy(alpha = 0.34f) else Color.White.copy(alpha = 0.03f))
.glideBorder(hot, Shape12, accent, pal.line2)
.alpha(if (enabled) 1f else 0.45f)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = androidx.compose.ui.semantics.Role.Button, enabled = enabled, onClick = onClick)
.controllerConfirm(enabled = enabled, onClick = onClick)
.semantics { contentDescription = label },
) {
Icon(icon, contentDescription = null, tint = if (danger && hot) colors.error else if (danger) Color.White else colors.onBackground, modifier = Modifier.size(18.dp))
}
}
@Composable
private fun RemoveButton(title: String, game: Library.SteamGame?, installed: InstalledStoreGame?, a: FrontEndActions) {
val ctx = LocalContext.current
val colors = MaterialTheme.colorScheme
if (installed != null) {
val key = installed.key
val gone = StoresState.uninstalling[key]
if (gone != null) {
val uninstalling = stringResource(R.string.stores_uninstalling)
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(44.dp).clip(Shape12).background(Color.Black.copy(alpha = 0.34f)).semantics { contentDescription = uninstalling }) {
if (gone > 0f) CircularProgressIndicator(progress = { gone }, strokeWidth = 2.dp, color = colors.error, trackColor = colors.error.copy(alpha = 0.22f), modifier = Modifier.size(20.dp))
else CircularProgressIndicator(strokeWidth = 2.dp, color = colors.error, modifier = Modifier.size(20.dp))
}
return
}
val heading = stringResource(R.string.stores_uninstall_title, title)
val size = StoreSizes.onDisk[key]
val body = if (size != null && size > 0) stringResource(R.string.stores_uninstall_body_size, formatBytes(size)) else stringResource(R.string.stores_uninstall_body)
val keep = stringResource(R.string.stores_notification_keep)
val remove = stringResource(R.string.stores_uninstall)
val busy = StoresState.download(key)?.isActive == true || (installed.sidecar.store == Store.XBOX && XboxContent.busy(installed.sidecar.id))
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
Box(Modifier.onGloballyPositioned { placed[0] = it }) {
IconAction(Icons.Outlined.Delete, remove, "game:remove", danger = true, enabled = !busy) {
val at = placed[0]?.takeIf { it.isAttached }?.boundsInRoot() ?: return@IconAction
var confirmed = false
Steps.ask(StepAsk(anchor = at, side = StepSide.Left, accent = colors.error, items = 3, pillCorner = 12.dp, maxWidth = 340.dp,
onDismiss = { if (confirmed) StoresState.uninstall(ctx, installed) { StoresState.refresh(ctx); a.onLibraryChanged() } },
handle = { Icon(Icons.Outlined.Delete, null, tint = colors.error, modifier = Modifier.size(18.dp)) }) {
StepTitle(heading)
Text(body, fontSize = 13.sp, lineHeight = 18.sp, color = colors.onSurfaceVariant, modifier = Modifier.stepItem(1).padding(start = 4.dp, bottom = 12.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), modifier = Modifier.fillMaxWidth().stepItem(2)) {
StepChoice(keep, enabled = open, modifier = Modifier.focusRequester(first)) { Steps.fold() }
StepChoice(remove, danger = true, enabled = open) { confirmed = true; Steps.fold() }
}
})
}
}
return
}
val g = game ?: return
val kind = GameRemoval.kind(g)
if (kind == GameRemoval.Kind.LAUNCHER || g.gameFiles?.isDirectory != true) return
var asking by remember(g.gameId) { mutableStateOf(false) }
val shortcut = kind == GameRemoval.Kind.SHORTCUT
val label = stringResource(if (shortcut) R.string.edit_remove else R.string.common_uninstall)
IconAction(Icons.Outlined.Delete, label, "game:remove", danger = true) { asking = true }
if (asking) ConfirmDialog(
title = if (shortcut) stringResource(R.string.games_remove_title, g.name) else stringResource(R.string.stores_uninstall_title, g.name),
text = when {
shortcut -> stringResource(R.string.games_remove_body)
g.library == Library.ADDED -> stringResource(R.string.stores_uninstall_body)
else -> stringResource(R.string.games_uninstall_body)
},
confirm = label,
onConfirm = { a.onGameRemoved(g) },
onDismiss = { asking = false },
)
}
@Composable
private fun InstallButton(label: String, modifier: Modifier = Modifier, onInstall: () -> Unit) {
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
Box(Modifier.onGloballyPositioned { placed[0] = it }) {
PrimaryButton(label, main = true, modifier = modifier) {
StoresMotion.markInstall(placed[0]?.takeIf { it.isAttached }?.boundsInRoot(), label)
onInstall()
}
}
}
@Composable
private fun AboutTab(developer: String, publisher: String, releaseDate: String, description: String, scores: GameScores?, stacked: Boolean) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.fillMaxWidth().tabPanel()) {
if (scores != null) Box(Modifier.readable()) { StoreGameFacts(developer, publisher, releaseDate, scores, scoresInHero = !stacked) }
else if (developer.isNotBlank() || publisher.isNotBlank() || releaseDate.isNotBlank()) {
val parts = listOf(developer, publisher, com.droiddeck.launcher.stores.GameFacts.releaseLabel(releaseDate).orEmpty()).filter { it.isNotBlank() }.distinct()
Text(parts.joinToString(" · "), fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.readable())
}
if (description.isNotBlank()) StoreAbout(description, AboutHeight, Modifier.fillMaxWidth())
}
}
private val AboutHeight = 260.dp
@Composable
internal fun Modifier.tabPanel(): Modifier {
val shape = RoundedCornerShape(16.dp)
return clip(shape).background(MaterialTheme.colorScheme.surface).border(1.dp, LocalPalette.current.line, shape).padding(horizontal = 16.dp, vertical = 14.dp)
}
@Composable
private fun StoreAbout(text: String, height: Dp, modifier: Modifier = Modifier) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val scroll = rememberScrollState()
val scope = rememberCoroutineScope()
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val shape = RoundedCornerShape(12.dp)
Box(
modifier.heightIn(max = height).clip(shape)
.glideBorder(hot, shape, pal.signal, restColor = Color.Transparent)
.paneItem("about")
.focusable(interactionSource = src)
.onPreviewKeyEvent { e ->
val step = when (e.nativeKeyEvent.keyCode) {
android.view.KeyEvent.KEYCODE_DPAD_DOWN -> if (scroll.value < scroll.maxValue) 120f else return@onPreviewKeyEvent false
android.view.KeyEvent.KEYCODE_DPAD_UP -> if (scroll.value > 0) -120f else return@onPreviewKeyEvent false
else -> return@onPreviewKeyEvent false
}
if (e.nativeKeyEvent.action == android.view.KeyEvent.ACTION_DOWN) scope.launch { scroll.animateScrollBy(step) }
true
},
) {
Text(text, fontSize = 14.sp, lineHeight = 21.sp, color = colors.onSurfaceVariant, modifier = Modifier.verticalScroll(scroll).padding(horizontal = 4.dp, vertical = 6.dp))
if (scroll.value < scroll.maxValue) Box(
Modifier.align(Alignment.BottomCenter).fillMaxWidth().height(28.dp)
.background(Brush.verticalGradient(listOf(Color.Transparent, colors.surface))),
)
}
}
@Composable
private fun LaunchTab(game: Library.SteamGame, s: FrontEndState, a: FrontEndActions, hopRail: String?) {
val host = rememberMenuHost()
Column(verticalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.fillMaxWidth()) {
LaunchSettings(s, a, host, game, hopRail ?: GameRef.key(game))
val folder = game.gameFiles
if (game.library == Library.ADDED && game.source == Library.ADDED && !s.shortcutPicker && folder != null) ShortcutFields(game, folder)
}
}
@Composable
private fun ShortcutFields(game: Library.SteamGame, folder: File) {
val ctx = LocalContext.current
val preview = remember(game.gameId, game.name, GamesDialogState.editing) { EditPreview.of(ctx, game, folder.path) }
fun edit(target: Boolean) { GamesDialogState.editing = EditRequest(folder.path, openTarget = target, fromSummary = false, preview = EditPreview.of(ctx, game, folder.path)) }
Column(Modifier.fillMaxWidth().tabPanel(), verticalArrangement = Arrangement.spacedBy(2.dp)) {
SectionLabel(stringResource(R.string.game_launch_shortcut))
MenuItem(stringResource(R.string.edit_name), checked = false, detail = preview.name) { edit(false) }
MenuItem(stringResource(R.string.edit_target), checked = false, detail = preview.exeName ?: "—") { edit(true) }
MenuItem(stringResource(R.string.edit_start_in), checked = false, detail = preview.startIn ?: "—") { edit(false) }
MenuItem(stringResource(R.string.edit_launch_options), checked = false, detail = preview.launchOptions.ifEmpty { "—" }) { edit(false) }
}
}
@Composable
private fun SavesTab(g: Library.SteamGame, prefix: File, a: FrontEndActions) {
val saves by produceState<List<GameSaves.SaveDir>?>(null, g.gameId) {
value = withContext(Dispatchers.IO) { runCatching { GameSaves.locate(prefix, g) }.getOrDefault(emptyList()) }
}
val found = saves
val summary = when {
found == null -> stringResource(R.string.games_saves_looking)
found.isEmpty() -> stringResource(R.string.games_saves_none)
else -> {
val mb = found.sumOf { it.bytes } / 1048576.0
stringResource(
R.string.games_saves_summary,
if (found.size == 1) found[0].relPath else stringResource(R.string.games_saves_folders, found.size),
found.sumOf { it.files },
if (mb < 1) stringResource(R.string.games_kb, (mb * 1024).toInt()) else stringResource(R.string.common_size_mb, mb),
)
}
}
Column(Modifier.fillMaxWidth().tabPanel(), verticalArrangement = Arrangement.spacedBy(2.dp)) {
Text(summary, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.padding(bottom = 6.dp).readable().padding(4.dp))
if (Store.byId(g.source).let { it == Store.GOG || it == Store.EPIC }) {
SectionLabel(stringResource(R.string.store_cloud_saves))
CloudRows(g)
SectionLabel(stringResource(R.string.games_saves))
}
MenuItem(stringResource(R.string.games_import_saves), checked = false) { a.onSaveImport(g) }
MenuItem(stringResource(R.string.games_export_gamehub), checked = false) { a.onSaveExport(g, GameSaves.Layout.GAMEHUB) }
MenuItem(stringResource(R.string.games_export_winlator), checked = false) { a.onSaveExport(g, GameSaves.Layout.WINLATOR) }
found?.firstOrNull()?.let { d ->
MenuItem(stringResource(R.string.games_open_save_folder), checked = false, detail = d.relPath) {
a.onBrowseFiles(File(prefix, "drive_c/users/steamuser/" + d.relPath))
}
}
}
}
@Composable
private fun CloudRows(g: Library.SteamGame) {
val store = Store.byId(g.source)?.takeIf { it != Store.AMAZON } ?: return
val id = g.storeId ?: return
val context = LocalContext.current.applicationContext
var busy by remember(g.gameId) { mutableStateOf<String?>(null) }
var tick by remember(g.gameId) { mutableStateOf(0) }
val status by produceState<CloudSaves.Status?>(null, g.gameId, tick) {
value = withContext(Dispatchers.IO) { runCatching { CloudSaves.status(context, store, id) }.getOrNull() }
}
val folder = g.gameFiles
var enabled by remember(g.gameId) { mutableStateOf(folder?.let { StoreGameSidecar.read(it)?.cloud } ?: true) }
val st = status
val supported = st?.supported ?: true
val conflict = st != null && st.conflicts.isNotEmpty()
fun sync(up: Boolean, force: Boolean = false) {
if (busy != null) return
busy = if (up) "up" else "down"
Thread({
val r = if (up) CloudSaves.upload(context, store, id, force) else CloudSaves.download(context, store, id, force)
StoresState.post { busy = null; tick++; android.widget.Toast.makeText(context, cloudMessage(context, r), android.widget.Toast.LENGTH_SHORT).show() }
}, "cloud-manual").start()
}
MenuItem(stringResource(R.string.store_cloud_saves), checked = enabled && supported, enabled = supported,
detail = if (!supported) stringResource(R.string.cloud_none) else null) {
val f = folder ?: return@MenuItem
val next = !enabled
Thread({
val written = runCatching { StoreGameSidecar.updateCloud(f, next) }.getOrNull()
StoresState.post { enabled = written?.cloud ?: enabled }
}, "cloud-switch").start()
}
val ready = st != null && supported && enabled && busy == null
val pending = stringResource(R.string.cloud_pending)
val summary = when {
st == null || busy != null -> pending
!supported -> stringResource(R.string.cloud_none)
conflict -> stringResource(R.string.cloud_conflict_files, st.conflicts.size)
st.lastSync > 0 -> stringResource(R.string.cloud_last_sync, android.text.format.DateUtils.getRelativeTimeSpanString(st.lastSync).toString())
else -> stringResource(R.string.cloud_never)
}
MenuItem(stringResource(if (conflict) R.string.cloud_keep_local else R.string.cloud_upload), checked = false, enabled = ready, detail = summary) { sync(up = true, force = conflict) }
MenuItem(stringResource(if (conflict) R.string.cloud_keep_cloud else R.string.cloud_download), checked = false, enabled = ready, detail = if (st == null || busy != null) pending else null) { sync(up = false, force = conflict) }
}
@Composable
private fun InstallTab(game: Library.SteamGame?, installed: InstalledStoreGame?, item: CatalogItem?, store: Store?) {
val ctx = LocalContext.current
val folder = installed?.folder ?: game?.gameFiles
val sidecar = installed?.sidecar
val size = folder?.let { diskSize(installed, it) }
val rows = buildList {
add(stringResource(R.string.game_install_location) to when {
installed != null -> StoreInstallRoot.labelFor(ctx, installed.folder)
game != null -> libraryLabel(game).ifBlank { stringResource(R.string.games_internal) }
else -> ""
})
if (folder != null) {
add(stringResource(R.string.game_install_folder) to folder.path)
add(stringResource(R.string.game_install_size) to (size?.let { formatBytes(it) } ?: stringResource(R.string.cloud_pending)))
}
sidecar?.extra?.get(StoreLanguage.EXTRA)?.takeIf { it.isNotBlank() }?.let { add(stringResource(R.string.stores_install_language) to StoreLanguage.displayName(it)) }
sidecar?.installVersion?.takeIf { it.isNotBlank() }?.let { add(stringResource(R.string.game_install_version) to it) }
sidecar?.installedAt?.takeIf { it > 0 }?.let {
add(stringResource(R.string.game_install_date) to java.text.DateFormat.getDateInstance(java.text.DateFormat.MEDIUM).format(java.util.Date(it)))
}
if (game != null) add(stringResource(R.string.game_install_played) to (playedSpan(game.lastPlayed)?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.games_never_played)))
if (store == Store.XBOX) {
val access = sidecar?.extra?.get("access") ?: if (item?.subscription == true) "gamepass" else if (item?.owned == true) "owned" else null
if (access != null) add(stringResource(R.string.game_install_access) to stringResource(if (access == "gamepass") R.string.stores_game_pass else R.string.game_install_access_owned))
}
}.filter { it.second.isNotBlank() }
Column(Modifier.fillMaxWidth().tabPanel(), verticalArrangement = Arrangement.spacedBy(2.dp)) {
for ((label, value) in rows) FactRow(label, value)
}
}
/** Text a controller has to land on to bring it into view: focusable, with the focus ring. */
@Composable
internal fun Modifier.readable(): Modifier {
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val shape = RoundedCornerShape(8.dp)
return clip(shape).glideBorder(hot, shape, pal.signal, restColor = Color.Transparent).focusable(interactionSource = src)
}
@Composable
private fun FactRow(label: String, value: String) {
Row(
horizontalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxWidth().readable().padding(horizontal = 8.dp, vertical = 7.dp),
) {
Text(label, fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, modifier = Modifier.width(150.dp))
Text(value, fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = MaterialTheme.colorScheme.onBackground, modifier = Modifier.weight(1f), maxLines = 2, overflow = TextOverflow.Ellipsis)
}
}
/** Store installs are measured by [StoreSizes]; Steam and added games by walking their folder once. */
@Composable
internal fun diskSize(installed: InstalledStoreGame?, folder: File): Long? {
val ctx = LocalContext.current
if (installed != null) {
LaunchedEffect(installed.key) { StoreSizes.requestDisk(ctx, installed.key, installed) }
return StoreSizes.onDisk[installed.key]
}
LaunchedEffect(folder.path) { FolderSizes.request(folder) }
return FolderSizes.known[folder.path]
}
internal object FolderSizes {
val known = androidx.compose.runtime.mutableStateMapOf<String, Long>()
private val asked = java.util.Collections.synchronizedSet(HashSet<String>())
fun request(folder: File) {
if (!asked.add(folder.path)) return
Thread({
var total = 0L
val walked = runCatching {
java.nio.file.Files.walkFileTree(folder.toPath(), object : java.nio.file.SimpleFileVisitor<java.nio.file.Path>() {
override fun visitFile(file: java.nio.file.Path, attrs: java.nio.file.attribute.BasicFileAttributes): java.nio.file.FileVisitResult {
if (attrs.isRegularFile) total += attrs.size()
return java.nio.file.FileVisitResult.CONTINUE
}
override fun visitFileFailed(file: java.nio.file.Path, exc: java.io.IOException): java.nio.file.FileVisitResult = java.nio.file.FileVisitResult.CONTINUE
})
}.isSuccess
if (walked) StoresState.post { known[folder.path] = total } else asked.remove(folder.path)
}, "folder-size").start()
}
}
@@ -0,0 +1,690 @@
package com.droiddeck.launcher.ui
import android.view.KeyEvent
import androidx.activity.compose.BackHandler
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.foundation.LocalIndication
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.focusGroup
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyGridState
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.FilterList
import androidx.compose.material.icons.filled.GridView
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Sort
import androidx.compose.material.icons.filled.ViewList
import androidx.compose.material.icons.filled.ViewModule
import androidx.compose.material.icons.outlined.Download
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.pluralStringResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role
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 coil.compose.AsyncImage
import com.droiddeck.launcher.R
import com.droiddeck.launcher.frontend.Library
import com.droiddeck.launcher.session.SessionPrefs
import com.droiddeck.launcher.stores.CatalogItem
import com.droiddeck.launcher.stores.Store
import com.droiddeck.launcher.stores.StoreSizes
import com.droiddeck.launcher.stores.StoresState
import com.droiddeck.launcher.stores.download.DownloadEntry
import com.droiddeck.launcher.stores.formatBytes
import com.droiddeck.launcher.stores.retainOrder
import com.droiddeck.launcher.stores.xbox.XboxLibraryFilter
import com.droiddeck.launcher.stores.sortGames
import kotlinx.coroutines.launch
import androidx.compose.runtime.setValue
/** One owned game: installed ones carry their [game], store products not installed yet only their [item]. */
internal class LibraryEntry(
val key: String, val source: String, val title: String,
val game: Library.SteamGame?, val item: CatalogItem?, val installed: Boolean,
) {
val storeKey: String? get() = item?.key ?: game?.storeId?.let { "${game.source}:$it" }
}
/** Installed games first claim their store product, so a game owned and installed shows once. */
internal fun libraryEntries(
games: List<Library.SteamGame>, owned: Map<Store, List<CatalogItem>>, installedStore: Set<String>,
): List<LibraryEntry> {
val claimed = HashSet<String>()
val out = ArrayList<LibraryEntry>()
for (g in games) {
if (g.library == Library.EPIC_LAUNCHER) continue
val storeKey = g.storeId?.let { "${g.source}:$it" }
val item = storeKey?.let { k -> Store.byId(g.source)?.let { st -> owned[st]?.firstOrNull { it.key == k } } }
if (storeKey != null) claimed += storeKey
out += LibraryEntry(GameRef.key(g), g.source, g.name, g, item, installed = true)
}
for (store in Store.entries) for (item in owned[store].orEmpty()) {
if (!claimed.add(item.key)) continue
out += LibraryEntry(GameRef.key(item), store.id, item.title, null, item, installed = item.key in installedStore)
}
return out.distinctBy { it.key }
}
internal fun installedFirst(entries: List<LibraryEntry>): List<LibraryEntry> = entries.partition { it.installed }.let { (on, off) -> on + off }
internal const val LIBRARY_ALL = "all"
internal val LIBRARY_SOURCES = listOf(Library.SOURCE_STEAM) + Store.entries.map { it.id } + Library.ADDED
/** Survives leaving the page, so Stores can open the Library on one store's owned games. */
internal object LibraryView {
var source by mutableStateOf(LIBRARY_ALL)
var query by mutableStateOf("")
var focusFirst = false
fun showOwned(store: Store) {
source = store.id
query = ""
focusFirst = true
}
}
@Composable
internal fun LibraryPage(s: FrontEndState, a: FrontEndActions, selected: String, onSelect: (String) -> Unit, modifier: Modifier) {
val ctx = LocalContext.current
val narrow = LocalNarrowPane.current
val ff = LocalFrontFocus.current
val focusManager = androidx.compose.ui.platform.LocalFocusManager.current
val inputMode = androidx.compose.ui.platform.LocalInputModeManager.current
LaunchedEffect(Unit) { StoresState.refresh(ctx) }
val stores = s.gameStoresEnabled && !s.shortcutPicker
val signedIn = Store.entries.filter { StoresState.isSignedIn(it) }
LaunchedEffect(stores, signedIn) { if (stores) signedIn.forEach { StoresState.loadLibrary(ctx, it) } }
val owned = if (stores) signedIn.associateWith { StoresState.library[it].orEmpty() } else emptyMap()
val installedStore = StoresState.installed.map { it.key }.toSet()
val syncing = if (stores) signedIn.mapNotNull { st -> StoresState.status[st]?.let { st to it } } else emptyList()
val cloudRows = rememberXboxCloudRows(s)
val all = remember(s.steamGames, cloudRows, owned, installedStore) { libraryEntries(s.steamGames + cloudRows, owned, installedStore) }
LaunchedEffect(Unit) { XboxLibraryFilter.load(ctx) }
val sources = LIBRARY_SOURCES.filter { src -> all.any { it.source == src } }
val source = LibraryView.source.takeIf { it == LIBRARY_ALL || it in sources } ?: LIBRARY_ALL
val q = LibraryView.query.trim().lowercase()
val xboxFilter = if (source == Store.XBOX.id) XboxLibraryFilter.current else XboxLibraryFilter.ALL
val candidates = all.filter {
(source == LIBRARY_ALL || it.source == source) && (q.isEmpty() || it.title.lowercase().contains(q)) && keepsXbox(xboxFilter, it)
}
val sort = s.storesSort
val sizeSort = sort == "sizeAsc" || sort == "sizeDesc"
val installedAt = StoresState.installed.associate { it.key to it.sidecar.installedAt }
val lastPlayed = s.steamGames.filter { it.storeId != null }.associate { "${it.source}:${it.storeId}" to it.lastPlayed }
fun sizeOf(e: LibraryEntry): Long? {
val key = e.storeKey
return when {
key != null && key in installedStore -> StoreSizes.onDisk[key]
e.game?.gameFiles != null -> FolderSizes.known[e.game.gameFiles.path]
e.item != null -> StoreSizes.size(e.item).takeIf { it > 0 }
else -> 0L
}
}
LaunchedEffect(sizeSort, candidates.map { it.key }) {
if (!sizeSort) return@LaunchedEffect
for (e in candidates) {
val key = e.storeKey
val inst = key?.let { k -> StoresState.installed.firstOrNull { it.key == k } }
when {
inst != null -> StoreSizes.requestDisk(ctx, inst.key, inst)
e.game?.gameFiles != null -> FolderSizes.request(e.game.gameFiles)
e.item != null && e.item.accessible -> StoreSizes.request(ctx, e.item)
}
}
}
var sizesWaited by remember(sizeSort) { mutableStateOf(false) }
LaunchedEffect(sizeSort) { if (sizeSort) { kotlinx.coroutines.delay(8_000); sizesWaited = true } }
val sizesPending = sizeSort && !sizesWaited && candidates.any { sizeOf(it) == null }
val sorted = sortGames(
candidates, sort, title = { it.title }, releaseDate = { it.item?.releaseDate.orEmpty() },
lastPlayed = { e -> e.game?.lastPlayed ?: e.storeKey?.let { lastPlayed[it] } ?: 0L },
installedAt = { e -> e.storeKey?.let { installedAt[it] } ?: 0L },
size = { e -> sizeOf(e) ?: 0L },
)
var stable by remember { mutableStateOf(emptyList<LibraryEntry>()) }
val ordered = if (sizesPending) retainOrder(candidates, stable) { it.key } else sorted
SideEffect { if (!sizesPending) stable = sorted }
val visible = installedFirst(ordered)
val split = visible.count { it.installed }.takeIf { it in 1 until visible.size } ?: 0
val open = selected.takeIf { it.startsWith("app:") }
val back = { onSelect("games") }
BackHandler(enabled = open != null) { back() }
val lead = remember { FocusRequester() }
var detailFocused by remember { mutableStateOf(false) }
var lastOpened by remember { mutableStateOf<String?>(null) }
val wasOpen = remember { booleanArrayOf(false) }
val backLabel = stringResource(R.string.rail_games)
LaunchedEffect(open) {
val closing = open == null && wasOpen[0]
wasOpen[0] = open != null
if (open != null) lastOpened = open
else if (!closing) return@LaunchedEffect
if (inputMode.inputMode != androidx.compose.ui.input.InputMode.Keyboard || ff == null) return@LaunchedEffect
repeat(60) { if (!HeldKeys.confirm) return@repeat; androidx.compose.runtime.withFrameNanos { } }
if (open != null) {
val backId = "back:$backLabel"
repeat(45) { n ->
androidx.compose.runtime.withFrameNanos { }
if (detailFocused) return@LaunchedEffect
val req = if (n < 20) lead else ff.items[backId]?.takeIf { (ff.attached[backId] ?: 0) > 0 } ?: lead
runCatching { req.requestFocus() }
}
return@LaunchedEffect
}
val id = lastOpened?.let { "card:$it" }
repeat(45) {
androidx.compose.runtime.withFrameNanos { }
if (id != null && ff.last == id) return@LaunchedEffect
val card = id?.let { ff.items[it] }?.takeIf { (ff.attached[id] ?: 0) > 0 }
if (card != null) runCatching { card.requestFocus() }
else if (ff.firstTileAttached > 0 && runCatching { ff.firstTile.requestFocus() }.isSuccess) return@LaunchedEffect
}
}
LaunchedEffect(Unit) {
if (!LibraryView.focusFirst || ff == null) return@LaunchedEffect
LibraryView.focusFirst = false
if (inputMode.inputMode != androidx.compose.ui.input.InputMode.Keyboard) return@LaunchedEffect
kotlinx.coroutines.delay(Motion.ms(240).toLong())
focusWithinFrames({ ff.last?.startsWith("card:") == true }) { if (ff.firstTileAttached > 0) ff.firstTile else FocusRequester.Default }
}
if (all.isEmpty() && open == null) {
LibraryEmpty(s, a, modifier)
return
}
val grid = remember { LazyGridState() }
val detailScroll = rememberScrollState()
var cycled by remember { mutableIntStateOf(0) }
var gridFocused by remember { mutableStateOf(false) }
LaunchedEffect(source, sort) { grid.scrollToItem(0) }
LaunchedEffect(cycled) {
if (cycled == 0 || ff == null || inputMode.inputMode != androidx.compose.ui.input.InputMode.Keyboard) return@LaunchedEffect
focusWithinFrames({ gridFocused }) {
if (ff.firstTileAttached > 0) ff.firstTile else ff.items["libchip:$source"] ?: FocusRequester.Default
}
}
CardDetailHost(
open, detailScroll,
gridScrolled = { grid.firstVisibleItemIndex > 0 || grid.firstVisibleItemScrollOffset > 0 },
onDetailFocus = { detailFocused = it },
modifier = modifier.padding(horizontal = if (narrow) 14.dp else 22.dp, vertical = if (narrow) 10.dp else 14.dp),
wrap = { key, content -> StoreTheme(GameRef.parse(key).let { r -> r.store ?: r.game(s.steamGames)?.source?.let { Store.byId(it) } }) { content() } },
grid = {
LibraryGrid(
s, a, grid, visible, split, all.size, sources, source, sizesPending, syncing,
onSource = { LibraryView.source = it },
onCycle = { step ->
val ids = listOf(LIBRARY_ALL) + sources
LibraryView.source = ids[(ids.indexOf(source).coerceAtLeast(0) + step + ids.size) % ids.size]
cycled++
},
onOpen = { focusManager.clearFocus(force = true); onSelect(it) },
searchCleared = open != null,
onFocus = { gridFocused = it },
)
},
) { key -> GameDetail(key, s, a, lead, backLabel, hopRail = "games", onBack = back) }
}
@Composable
private fun LibraryEmpty(s: FrontEndState, a: FrontEndActions, modifier: Modifier) {
val narrow = LocalNarrowPane.current
Column(modifier = modifier.padding(horizontal = if (narrow) 16.dp else 22.dp, vertical = if (narrow) 12.dp else 18.dp)) {
Rise(0) {
PageHeader(stringResource(R.string.rail_games)) {
if (s.steamGamesX64) ClientTag()
if (!s.shortcutPicker) AddGameButton { GamesDialogState.adding = true }
}
}
Rise(1) { Note(stringResource(if (s.shortcutPicker && s.shortcutLibraryScanning) R.string.game_shortcut_scanning else R.string.games_empty)) }
if (!s.shortcutPicker) Rise(2) {
Actions {
PrimaryButton(stringResource(R.string.games_play_steam), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow, modifier = Modifier.padding(top = 12.dp).testTag("play-steam"), onClick = a.onPlay)
}
}
Box(Modifier.padding(top = 12.dp)) { GameFileFolderActions(s, a) }
}
}
@Composable
private fun LibraryGrid(
s: FrontEndState, a: FrontEndActions, grid: LazyGridState, visible: List<LibraryEntry>, split: Int, total: Int,
sources: List<String>, source: String, sizesPending: Boolean, syncing: List<Pair<Store, String>>,
onSource: (String) -> Unit, onCycle: (Int) -> Unit, onOpen: (String) -> Unit, onFocus: (Boolean) -> Unit, searchCleared: Boolean,
) {
val listMode = s.storesView == "list"
val capsules = s.storesView == "capsules"
val tileWidth = if (capsules) CapsuleWidth else CardWidth
val focus = LocalFrontFocus.current
val scope = rememberCoroutineScope()
val density = androidx.compose.ui.platform.LocalDensity.current
var columns by remember { mutableIntStateOf(1) }
val headerItems = 2 + (if (sizesPending) 1 else 0) + syncing.size
val tile: @Composable (LibraryEntry) -> Unit = { e ->
val first = e.key == visible.firstOrNull()?.key
val navigation = if (focus == null) Modifier else Modifier.onPreviewKeyEvent { event ->
val key = event.nativeKeyEvent
val direction = when (key.keyCode) {
KeyEvent.KEYCODE_DPAD_DOWN -> 1
KeyEvent.KEYCODE_DPAD_UP -> -1
else -> 0
}
val index = visible.indexOfFirst { it.key == e.key }
val next = storeGridNeighbor(index, visible.size, if (listMode) 1 else columns, direction, split)
when {
direction == 0 || index < 0 -> false
next == null -> direction > 0
else -> {
if (key.action == KeyEvent.ACTION_DOWN) {
val target = "card:${visible[next].key}"
val targetIndex = headerItems + next + if (split == 0) 0 else if (next < split) 1 else 2
val shown = grid.layoutInfo.visibleItemsInfo.any { it.index == targetIndex }
if (!shown || (focus.attached[target] ?: 0) == 0 ||
runCatching { focus.items.getValue(target).requestFocus() }.isFailure) scope.launch {
grid.scrollToItem(targetIndex)
focusWithinFrames({ focus.last == target }) { focus.items[target] ?: FocusRequester.Default }
}
}
true
}
}
}
val picker = s.shortcutPicker && e.game != null
val choose = if (picker) ({ a.onSteamGame(e.game!!) }) else null
if (listMode) LibraryRow(e, first, navigation, choose, onOpen) else LibraryTile(e, capsules, first, navigation, choose, onOpen)
}
LazyVerticalGrid(
columns = if (listMode) GridCells.Fixed(1) else GridCells.Adaptive(tileWidth), state = grid,
modifier = Modifier.fillMaxSize().onFocusChanged { onFocus(it.hasFocus) }.focusGroup().bumpers(onPrevious = { onCycle(-1) }, onNext = { onCycle(1) }).onSizeChanged {
val gap = with(density) { 10.dp.roundToPx() }
val minimum = with(density) { (if (listMode) CardWidth * 2 else tileWidth).roundToPx() }
columns = ((it.width + gap) / (minimum + gap)).coerceAtLeast(1)
},
horizontalArrangement = Arrangement.spacedBy(10.dp), verticalArrangement = Arrangement.spacedBy(10.dp),
contentPadding = PaddingValues(top = 4.dp, bottom = 16.dp),
) {
item(key = "header", span = { GridItemSpan(maxLineSpan) }) { Rise(0) { LibraryHeader(s, a, total, searchCleared, xbox = source == Store.XBOX.id) } }
item(key = "filters", span = { GridItemSpan(maxLineSpan) }) {
Rise(1) { LibraryFilters(sources, source, onSource) }
}
for ((store, line) in syncing) item(key = "sync-${store.id}", span = { GridItemSpan(maxLineSpan) }) {
Column(verticalArrangement = Arrangement.spacedBy(4.dp), modifier = Modifier.fillMaxWidth()) {
Text(store.shortLabel + " · " + line, fontSize = 12.sp, color = MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
if (syncCount(line) != null) SyncBar(line)
else androidx.compose.material3.LinearProgressIndicator(Modifier.fillMaxWidth().padding(bottom = 8.dp).height(3.dp))
}
}
if (sizesPending) item(key = "size-sort-progress", span = { GridItemSpan(maxLineSpan) }) {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.fillMaxWidth().padding(bottom = 4.dp)) {
androidx.compose.material3.LinearProgressIndicator(Modifier.weight(1f).height(3.dp))
Text(stringResource(R.string.store_section_loading), fontSize = 11.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
val empty = R.string.stores_nothing_matches
if (split == 0) libraryGrid(visible, empty, tile)
else {
item(key = "installed-label", span = { GridItemSpan(maxLineSpan) }) { SectionLabel(stringResource(R.string.stores_section_installed)) }
libraryGrid(visible.take(split), empty, tile)
item(key = "not-installed-label", span = { GridItemSpan(maxLineSpan) }) { SectionLabel(stringResource(R.string.stores_section_not_installed)) }
libraryGrid(visible.drop(split), empty, tile)
}
}
}
private fun LazyGridScope.libraryGrid(entries: List<LibraryEntry>, empty: Int, tile: @Composable (LibraryEntry) -> Unit) {
if (entries.isEmpty()) item(key = "empty", span = { GridItemSpan(maxLineSpan) }) {
Box(Modifier.padding(vertical = 32.dp).fillMaxWidth(), contentAlignment = Alignment.Center) {
Text(stringResource(empty), fontSize = 13.sp, color = MaterialTheme.colorScheme.onSurfaceVariant)
}
}
else items(entries, key = { it.key }, contentType = { "game" }) { tile(it) }
}
@Composable
private fun LibraryHeader(s: FrontEndState, a: FrontEndActions, total: Int, searchCleared: Boolean, xbox: Boolean) {
val colors = MaterialTheme.colorScheme
val narrow = LocalNarrowPane.current
ModeHeader(
stringResource(R.string.rail_games), LibraryView.query, { LibraryView.query = it },
placeholder = stringResource(R.string.stores_search_library),
clearSearchFocus = searchCleared,
lead = {
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(start = 2.dp, end = 4.dp)) {
Text(stringResource(R.string.rail_games), fontSize = if (narrow) 20.sp else 24.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1)
Text(pluralStringResource(R.plurals.mode_added_count, total, total), fontSize = 13.sp, color = colors.onSurfaceVariant, maxLines = 1)
if (s.steamGamesX64 && !narrow) ClientTag()
}
},
) {
if (xbox) XboxFilterButton()
SortButton(s, a)
ViewButton(s, a)
GameFileFolderActions(s, a)
if (!s.shortcutPicker) AddGameButton { GamesDialogState.adding = true }
}
if (s.steamGamesX64 && narrow) Box(Modifier.padding(top = 4.dp)) { ClientTag() }
}
@Composable
private fun LibraryFilters(sources: List<String>, source: String, onSource: (String) -> Unit) {
if (sources.size < 2) return
Row(
horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth().horizontalScroll(rememberScrollState()).padding(top = 4.dp, bottom = 2.dp),
) {
SourceFilterChip(LIBRARY_ALL, stringResource(R.string.stores_tab_all), source == LIBRARY_ALL, dot = null) { onSource(LIBRARY_ALL) }
for (src in sources) {
val store = Store.byId(src)
val dot = when {
store != null -> sourceColours(src).dot.copy(alpha = if (StoresState.isSignedIn(store) && StoresState.expired[store] != true) 1f else 0.35f)
src == Library.SOURCE_STEAM -> sourceColours(src).dot
else -> null
}
SourceFilterChip(src, if (src == Library.ADDED) stringResource(R.string.library_filter_added) else store?.shortLabel ?: sourceLabel(src), source == src, dot) { onSource(src) }
}
}
}
@Composable
private fun SourceFilterChip(key: String, label: String, on: Boolean, dot: Color?, onClick: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val shape = RoundedCornerShape(99.dp)
Row(
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(7.dp),
modifier = Modifier.paneItem("libchip:$key").testTag("library-source-$key").heightIn(min = 40.dp).clip(shape)
.background(if (on) pal.signal.copy(alpha = 0.16f) else colors.surface)
.glideBorder(hot, shape, pal.signal, if (on) pal.signal else pal.line)
.hoverable(src).selectable(selected = on, interactionSource = src, indication = LocalIndication.current, role = Role.Tab, onClick = onClick)
.controllerConfirm(onClick = onClick)
.padding(horizontal = 14.dp, vertical = 8.dp),
) {
if (dot != null) Box(Modifier.size(9.dp).background(dot, CircleShape))
Text(label, fontSize = 13.sp, fontWeight = FontWeight.Bold, color = if (on || hot) colors.onBackground else colors.onSurfaceVariant, maxLines = 1)
}
}
private fun entryArt(e: LibraryEntry, tall: Boolean): Any? {
val inst = e.storeKey?.let { k -> StoresState.installed.firstOrNull { it.key == k } }
val wide: Any? = e.game?.hero ?: e.item?.imageUrl ?: inst?.sidecar?.hero ?: inst?.sidecar?.cover
val portrait: Any? = e.game?.art ?: e.item?.tallImageUrl ?: inst?.sidecar?.cover
return if (tall) portrait ?: wide else wide ?: portrait
}
@Composable
private fun LibraryTile(e: LibraryEntry, capsule: Boolean, first: Boolean, navigation: Modifier, choose: (() -> Unit)?, onOpen: (String) -> Unit) {
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val pressed by src.collectIsPressedAsState()
val scale by animateFloatAsState(if (pressed) 0.985f else 1f, Motion.sp(0.5f, Spring.StiffnessMedium), label = "tileScale")
val download = e.storeKey?.let { StoresState.download(it) }?.takeIf { it.isActive }
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
val corner = with(androidx.compose.ui.platform.LocalDensity.current) { 10.dp.toPx() }
val wide = entryArt(e, tall = false)
val tall = entryArt(e, tall = true)
RequestSize(e)
val open = {
val art = if (capsule) tall else wide
placed[0]?.takeIf { it.isAttached }?.let { StoresMotion.markCard(CardMark(e.key, it.boundsInRoot(), corner, (art as? java.io.File)?.path ?: art as? String)) }
onOpen(e.key)
}
Column(
modifier = Modifier.fillMaxWidth().onGloballyPositioned { placed[0] = it }
.then(navigation).paneItem("card:${e.key}").then(if (first) Modifier.firstTile().paneEntry() else Modifier).graphicsLayer { scaleX = scale; scaleY = scale }
.clip(RoundedCornerShape(10.dp)).glideBorder(hot, RoundedCornerShape(10.dp), pal.signal, pal.line)
.hoverable(src).clickable(role = Role.Button, interactionSource = src, indication = LocalIndication.current, onClick = open)
.controllerConfirm(onClick = choose ?: open),
) {
CardFace(e.title, wide, tall, download, hot, capsule, dim = !e.installed && download == null,
badge = { SourceChip(e.source, small = true, modifier = Modifier.align(Alignment.TopStart).padding(6.dp)) }) {
LibraryMarker(e, download)
}
}
}
@Composable
private fun RequestSize(e: LibraryEntry) {
val ctx = LocalContext.current
val item = e.item?.takeIf { !e.installed && it.accessible && it.sizeBytes <= 0 } ?: return
LaunchedEffect(item.key) { StoreSizes.request(ctx, item) }
}
@Composable
private fun LibraryMarker(e: LibraryEntry, download: DownloadEntry?) {
val pal = LocalPalette.current
when {
download != null -> Text("${download.percent}%", fontSize = 10.sp, fontWeight = FontWeight.Bold, color = pal.signal, maxLines = 1)
e.installed -> Unit
e.item != null && StoresState.isUnfinished(e.item) -> Text(stringResource(R.string.stores_resume), fontSize = 10.sp, fontWeight = FontWeight.Bold, color = pal.signal, maxLines = 1)
else -> Icon(Icons.Outlined.Download, stringResource(R.string.stores_section_not_installed), tint = Color.White, modifier = Modifier.size(12.dp))
}
}
@Composable
private fun LibraryRow(e: LibraryEntry, first: Boolean, navigation: Modifier, choose: (() -> Unit)?, onOpen: (String) -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
val download = e.storeKey?.let { StoresState.download(it) }?.takeIf { it.isActive }
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
val corner = with(androidx.compose.ui.platform.LocalDensity.current) { 10.dp.toPx() }
val wide = entryArt(e, tall = false)
val open = {
placed[0]?.takeIf { it.isAttached }?.let { StoresMotion.markCard(CardMark(e.key, it.boundsInRoot(), corner, (wide as? java.io.File)?.path ?: wide as? String)) }
onOpen(e.key)
}
RequestSize(e)
val inst = e.storeKey?.let { k -> StoresState.installed.firstOrNull { it.key == k } }
val folder = inst?.folder ?: e.game?.gameFiles
val size = if (e.installed && folder != null) diskSize(inst, folder) else e.item?.let { StoreSizes.size(it) }?.takeIf { it > 0 }
val status = when {
download != null -> downloadLabel(download)
!e.installed -> stringResource(R.string.stores_section_not_installed)
e.game != null -> playedSpan(e.game.lastPlayed)?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.games_never_played)
else -> stringResource(R.string.stores_installed_chip)
}
val meta = listOfNotNull(status, size?.let { formatBytes(it) }?.takeIf { it.isNotEmpty() })
Row(
verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth().heightIn(min = 64.dp).onGloballyPositioned { placed[0] = it }.then(navigation).paneItem("card:${e.key}")
.then(if (first) Modifier.firstTile().paneEntry() else Modifier)
.clip(RoundedCornerShape(10.dp)).glideBorder(hot, RoundedCornerShape(10.dp), pal.signal, pal.line)
.hoverable(src).clickable(role = Role.Button, interactionSource = src, indication = LocalIndication.current, onClick = open)
.controllerConfirm(onClick = choose ?: open)
.padding(horizontal = 10.dp, vertical = 8.dp),
) {
Box(Modifier.width(96.dp).height(54.dp).clip(RoundedCornerShape(8.dp)).background(artBrush(hueOf(e.title))).graphicsLayer { alpha = if (e.installed || download != null) 1f else 0.55f }) {
if (wide != null) AsyncImage(model = wide, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize())
}
Column(Modifier.weight(1f), verticalArrangement = Arrangement.spacedBy(3.dp)) {
Text(e.title, fontSize = 14.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis)
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp)) {
SourceChip(e.source, small = true)
Text(meta.joinToString(" · "), fontSize = 12.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
}
}
if (download != null) Text("${download.percent}%", fontSize = 11.sp, fontWeight = FontWeight.Bold, color = pal.signal, maxLines = 1)
else if (!e.installed) Icon(Icons.Outlined.Download, null, tint = colors.onSurfaceVariant, modifier = Modifier.size(16.dp))
}
}
@Composable
private fun HeaderIconButton(description: String, icon: androidx.compose.ui.graphics.vector.ImageVector, key: String, onClick: () -> Unit) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val src = remember { MutableInteractionSource() }
val hot = rememberHot(src)
Box(
contentAlignment = Alignment.Center,
modifier = Modifier.paneItem("btn:$key").size(32.dp).clip(CircleShape).background(if (hot) pal.signal.copy(alpha = 0.14f) else Color.Transparent)
.glideBorder(hot, CircleShape, pal.signal)
.hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, role = Role.Button, onClick = onClick)
.controllerConfirm(onClick = onClick)
.semantics { contentDescription = description },
) { Icon(icon, null, tint = if (hot) pal.signal else colors.onSurfaceVariant, modifier = Modifier.size(18.dp)) }
}
/** Choosing Library in the rail lands on the first game, as Play Steam does on the Steam page. */
@Composable
private fun Modifier.paneEntry(): Modifier {
val ff = LocalFrontFocus.current ?: return this
androidx.compose.runtime.DisposableEffect(ff) {
ff.primaryAttached++
onDispose { ff.primaryAttached-- }
}
return focusRequester(ff.primary)
}
internal fun keepsXbox(filter: String, e: LibraryEntry): Boolean = when {
filter == XboxLibraryFilter.ALL || e.source != Store.XBOX.id -> true
e.game?.library == Library.XBOX_CLOUD -> filter == XboxLibraryFilter.CLOUD
else -> e.item?.let { XboxLibraryFilter.keeps(filter, it) } == true
}
@Composable
private fun xboxFilterLabel(filter: String): String = when (filter) {
XboxLibraryFilter.OWNED -> stringResource(R.string.game_install_access_owned)
XboxLibraryFilter.GAME_PASS -> stringResource(R.string.xbox_filter_game_pass)
XboxLibraryFilter.CLOUD -> stringResource(R.string.xbox_filter_cloud)
else -> stringResource(R.string.stores_tab_all)
}
@Composable
private fun XboxFilterButton() {
val pal = LocalPalette.current
val ctx = LocalContext.current
val title = stringResource(R.string.xbox_filter_show)
var at by remember { mutableStateOf<Rect?>(null) }
Box(Modifier.onGloballyPositioned { at = it.boundsInRoot() }) {
HeaderIconButton(title + " · " + xboxFilterLabel(XboxLibraryFilter.current), Icons.Filled.FilterList, "xbox-filter") {
val from = at ?: return@HeaderIconButton
Steps.ask(StepAsk(
anchor = from, side = StepSide.Left, accent = pal.signal, pillCorner = 8.dp, items = XboxLibraryFilter.ALL_FILTERS.size,
handle = { Text(title, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = pal.signal, maxLines = 1, overflow = TextOverflow.Ellipsis) },
) {
StepTitle(title)
StepList {
XboxLibraryFilter.ALL_FILTERS.forEachIndexed { i, filter ->
StepChoice(
xboxFilterLabel(filter), selected = filter == XboxLibraryFilter.current, enabled = open,
modifier = Modifier.fillMaxWidth().stepItem(i).then(if (filter == XboxLibraryFilter.current) Modifier.focusRequester(first) else Modifier),
) {
XboxLibraryFilter.set(ctx, filter)
Steps.fold()
}
}
}
})
}
}
}
@Composable
private fun SortButton(s: FrontEndState, a: FrontEndActions) {
val pal = LocalPalette.current
val title = stringResource(R.string.stores_sort)
var at by remember { mutableStateOf<Rect?>(null) }
Box(Modifier.onGloballyPositioned { at = it.boundsInRoot() }) {
HeaderIconButton(title, Icons.Filled.Sort, "sort") {
val from = at ?: return@HeaderIconButton
Steps.ask(StepAsk(
anchor = from, side = StepSide.Left, accent = pal.signal, pillCorner = 8.dp, items = SessionPrefs.STORES_SORTS.size,
handle = { Text(title, fontSize = 12.sp, fontWeight = FontWeight.SemiBold, color = pal.signal, maxLines = 1, overflow = TextOverflow.Ellipsis) },
) {
StepTitle(title)
StepList {
SessionPrefs.STORES_SORTS.forEachIndexed { i, sort ->
val label = when (sort) {
"lastPlayed" -> stringResource(R.string.stores_sort_last_played)
"recentlyInstalled" -> stringResource(R.string.stores_sort_recently_installed)
"titleAsc" -> stringResource(R.string.stores_sort_title_asc)
"titleDesc" -> stringResource(R.string.stores_sort_title_desc)
"sizeDesc" -> stringResource(R.string.stores_sort_size_desc)
"sizeAsc" -> stringResource(R.string.stores_sort_size_asc)
else -> stringResource(R.string.stores_sort_release_date)
}
StepChoice(
label, selected = sort == s.storesSort, enabled = open,
modifier = Modifier.fillMaxWidth().stepItem(i).then(if (sort == s.storesSort) Modifier.focusRequester(first) else Modifier),
) {
a.onStoresSort(sort)
Steps.fold()
}
}
}
})
}
}
}
@Composable
private fun ViewButton(s: FrontEndState, a: FrontEndActions) {
val views = SessionPrefs.STORES_VIEWS
val next = views[(views.indexOf(s.storesView) + 1) % views.size]
HeaderIconButton(
stringResource(when (next) { "capsules" -> R.string.stores_view_capsules; "list" -> R.string.stores_view_list; else -> R.string.stores_view_cards }),
when (next) { "capsules" -> Icons.Filled.ViewModule; "list" -> Icons.Filled.ViewList; else -> Icons.Filled.GridView },
"view",
) { a.onStoresView(next) }
}
@@ -270,6 +270,23 @@ internal fun StoreAchievementsCardContent(label: String, state: AchievementsStat
}
}
@Composable
internal fun AchievementsTab(label: String, state: AchievementsState, modifier: Modifier = Modifier) {
val colors = MaterialTheme.colorScheme
val list = state.list
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = modifier.fillMaxWidth().tabPanel()) {
if (list.isNullOrEmpty()) {
Text(achievementsNote(state, label).orEmpty(), fontSize = 13.sp, color = colors.onSurfaceVariant, modifier = Modifier.readable().padding(4.dp))
return@Column
}
Box(Modifier.fillMaxWidth().readable().padding(4.dp)) { AchievementsProgress(state) }
for ((group, items) in groups(list)) {
AchievementGroupLabel(stringResource(group, items.size))
for (a in items) androidx.compose.runtime.key(a.id) { AchievementRow(a, compact = true) }
}
}
}
private fun LazyListScope.achievementItems(list: List<Achievement>, compact: Boolean, first: FocusRequester, labels: Boolean = true) {
var index = 0
for ((label, group) in groups(list)) {
@@ -1,329 +0,0 @@
package com.droiddeck.launcher.ui
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.layout.boundsInRoot
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.foundation.background
import androidx.compose.foundation.focusable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.gestures.animateScrollBy
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.input.key.onPreviewKeyEvent
import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.layout.onSizeChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import com.droiddeck.launcher.R
import com.droiddeck.launcher.stores.xbox.xboxCloud
import com.droiddeck.launcher.stores.xbox.xboxPc
import com.droiddeck.launcher.stores.Store
import com.droiddeck.launcher.stores.StoreAchievements
import com.droiddeck.launcher.stores.StoresState
import com.droiddeck.launcher.stores.download.DownloadState
import com.droiddeck.launcher.stores.formatBytes
@Composable
internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: FrontEndActions, lead: androidx.compose.ui.focus.FocusRequester, onBack: () -> Unit) {
val leadOn = Modifier.focusRequester(lead)
val ctx = LocalContext.current
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val narrow = LocalNarrowPane.current
val stacked = narrow || LocalConfiguration.current.screenWidthDp < 700
val id = key.substringAfter(':')
val item = StoresState.detailItem(store, id)
val installed = StoresState.installedGame(store, id)
val rawDownload = StoresState.download("${store.id}:$id")
val download = rawDownload?.takeIf { it.isActive }
val failedDownload = rawDownload?.takeIf { it.state == DownloadState.FAILED }
val title = item?.title ?: installed?.sidecar?.title ?: id
Rise(0) {
Box(Modifier.padding(bottom = 12.dp)) { BackLink(stringResource(R.string.common_back), compact = true, onClick = onBack) }
}
val scores = if (store == Store.GOG || store == Store.EPIC) rememberGameScores(title.takeIf { it != id }) else null
val hero: @Composable () -> Unit = {
androidx.compose.runtime.DisposableEffect(Unit) { onDispose { StoresMotion.heroAt = null } }
Box(
Modifier.fillMaxWidth().heightIn(min = if (narrow) 150.dp else 180.dp)
.onGloballyPositioned { StoresMotion.heroAt = it.boundsInRoot() }
.clip(Shape16).background(artBrush(hueOf(title))),
) {
val art = item?.imageUrl ?: installed?.sidecar?.hero ?: installed?.sidecar?.cover
// The card it came from keeps its own bitmap under the hero until the wide art is decoded.
val heroArt = cardArtRequest(art, item?.tallImageUrl)
if (heroArt != null) AsyncImage(model = heroArt, contentDescription = null, contentScale = ContentScale.Crop, modifier = Modifier.matchParentSize())
Spacer(Modifier.matchParentSize().background(Brush.horizontalGradient(0f to colors.background.copy(alpha = 0.94f), 0.5f to colors.background.copy(alpha = 0.55f), 1f to Color.Transparent)))
if (scores != null && !stacked) {
Spacer(Modifier.matchParentSize().background(Brush.horizontalGradient(0.45f to Color.Transparent, 1f to colors.background.copy(alpha = 0.75f))))
HeroScores(scores, Modifier.align(Alignment.BottomEnd).padding(end = 16.dp, bottom = 18.dp))
}
Column(verticalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.align(Alignment.BottomStart).padding(horizontal = 22.dp, vertical = 18.dp)) {
val passName = item?.includedWith.orEmpty()
val state = when {
installed != null -> stringResource(R.string.stores_eyebrow_installed) + " · " + com.droiddeck.launcher.stores.StoreInstallRoot.labelFor(ctx, installed.folder)
download != null && download.location.isNotBlank() -> stringResource(R.string.stores_eyebrow_library) + " · " + download.location
failedDownload != null && failedDownload.location.isNotBlank() -> stringResource(R.string.stores_eyebrow_library) + " · " + failedDownload.location
item?.owned == true -> stringResource(R.string.stores_eyebrow_library)
item?.subscription == true -> passName.ifBlank { "PC Game Pass" }
passName.isNotBlank() -> "Available with $passName"
item?.accessible == true -> stringResource(R.string.stores_eyebrow_library)
else -> stringResource(R.string.stores_eyebrow_store)
}
val size = (item?.sizeBytes ?: 0L).takeIf { it > 0 }?.let { " · " + formatBytes(it) } ?: ""
Text((state + size).uppercase(), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = pal.signal)
Text(title, fontSize = if (narrow) 24.sp else 30.sp, lineHeight = if (narrow) 27.sp else 33.sp, fontWeight = FontWeight.Black, color = colors.onBackground, maxLines = 2, overflow = TextOverflow.Ellipsis)
if (!failedDownload?.error.isNullOrBlank()) {
Text(
failedDownload!!.error!!,
fontSize = 13.sp,
lineHeight = 17.sp,
fontWeight = FontWeight.Medium,
color = colors.error,
maxLines = 3,
overflow = TextOverflow.Ellipsis
)
}
Actions {
when {
installed != null -> {
val gone = StoresState.uninstalling["${store.id}:$id"]
PrimaryButton(stringResource(R.string.stores_play), enabled = !s.busy && gone == null && download == null && (store != Store.XBOX || !com.droiddeck.launcher.stores.xbox.XboxContent.busy(id)), main = true, icon = Icons.Filled.PlayArrow, modifier = leadOn) {
launchStoreGame(ctx, store, id, s, a)
}
ActionChip(stringResource(R.string.stores_in_steam), ok = true)
if (store == Store.XBOX && item?.xboxCloud == true) XboxStreamButton(item)
if (store == Store.XBOX) {
SecondaryButton("Update / verify", enabled = download == null && !com.droiddeck.launcher.stores.xbox.XboxRuntime.isActive(id)) {
runCatching { com.droiddeck.launcher.stores.xbox.XboxBackend.update(ctx, installed) }
.onFailure { StoresState.problems[Store.XBOX] = it.message ?: "Close this Xbox game before updating it." }
}
SecondaryButton("Manage content", enabled = download == null) {
com.droiddeck.launcher.stores.xbox.XboxContent.manage(ctx, installed)
}
}
if (gone != null) SecondaryButton(stringResource(R.string.stores_uninstalling), enabled = false, progress = gone) {}
else UninstallButton(title, installed, enabled = download == null && (store != Store.XBOX || !com.droiddeck.launcher.stores.xbox.XboxContent.busy(id))) {
StoresState.uninstall(ctx, installed) { StoresState.refresh(ctx); a.onLibraryChanged() }
}
}
download != null -> {
ActionChip(downloadLabel(download), ok = false)
if (download.state != DownloadState.PAUSED) SecondaryButton(stringResource(R.string.stores_dl_pause), modifier = leadOn) { com.droiddeck.launcher.stores.download.DownloadQueue.pause(download.key) }
else PrimaryButton(stringResource(R.string.stores_dl_resume), main = true, modifier = leadOn) { com.droiddeck.launcher.stores.download.DownloadQueue.resume(ctx, download.key) }
ConfirmButton(stringResource(R.string.stores_dl_cancel), stringResource(R.string.stores_dl_cancel_confirm)) { com.droiddeck.launcher.stores.download.DownloadQueue.cancel(ctx, download.key) }
}
failedDownload != null -> {
ActionChip(stringResource(R.string.stores_dl_failed), ok = false)
PrimaryButton(stringResource(R.string.stores_dl_resume), main = true, modifier = leadOn) {
if (item != null) StoresState.requestInstall(ctx, item)
else com.droiddeck.launcher.stores.download.DownloadQueue.retry(ctx, failedDownload.key)
}
ConfirmButton(stringResource(R.string.stores_dl_clear), stringResource(R.string.stores_dl_cancel_confirm), compact = true) {
com.droiddeck.launcher.stores.download.DownloadQueue.clear(ctx, failedDownload.key)
}
}
store == Store.XBOX && item != null && !item.xboxPc && item.xboxCloud -> {
XboxStreamButton(item, main = true, modifier = leadOn)
XboxAddToGamesButton(item)
}
item?.installable == true && StoresState.isUnfinished(item) -> {
InstallButton(stringResource(R.string.stores_resume_install), leadOn) { StoresState.requestInstall(ctx, item) }
ConfirmButton(stringResource(R.string.stores_dl_clear), stringResource(R.string.stores_dl_cancel_confirm), compact = true) { StoresState.clearUnfinished(ctx, item) }
}
item?.installable == true -> {
InstallButton(
if (item.sizeBytes > 0) stringResource(R.string.stores_install_size, formatBytes(item.sizeBytes)) else stringResource(R.string.stores_install),
leadOn,
) { StoresState.requestInstall(ctx, item) }
if (store == Store.XBOX && item.xboxCloud) {
XboxStreamButton(item)
XboxAddToGamesButton(item)
}
if (!item.accessible) {
SecondaryButton(
when {
item.isFree -> stringResource(R.string.stores_get_free)
item.hasPrice && item.finalPrice.isNotBlank() -> stringResource(R.string.stores_buy_price, item.finalPrice)
else -> stringResource(R.string.stores_view_on, store.shortLabel)
},
) { openStoreUrl(ctx, item) }
}
}
item != null && item.isFree -> PrimaryButton(stringResource(R.string.stores_get_free), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
item != null -> PrimaryButton(if (item.hasPrice && item.finalPrice.isNotBlank()) stringResource(R.string.stores_buy_price, item.finalPrice) else stringResource(R.string.stores_view_on, store.shortLabel), main = true, modifier = leadOn) { openStoreUrl(ctx, item) }
}
BusyChip(s)
}
}
}
}
if (StoresMotion.heroLands) hero() else Rise(1) { hero() }
if (scores != null) Rise(2) {
val listed = (StoresState.found[store].orEmpty() + StoresState.shelves[store]?.all.orEmpty()).firstOrNull { it.id == id }
val developer = item?.developer?.ifBlank { null } ?: listed?.developer.orEmpty()
val publisher = item?.publisher?.ifBlank { null } ?: listed?.publisher.orEmpty()
val releaseDate = item?.releaseDate?.ifBlank { null } ?: listed?.releaseDate.orEmpty()
StoreGameFacts(developer, publisher, releaseDate, scores, scoresInHero = !stacked)
}
val description = item?.let { com.droiddeck.launcher.stores.cleanStoreText(it.description) }.orEmpty()
.takeUnless { it.trim().equals(title.trim(), ignoreCase = true) }.orEmpty()
val achievements = remember(store, id, item?.accessible, installed) {
when {
StoreAchievements.forSource(store.id) == null -> null
item?.accessible == true -> StoreAchievements.Game.of(item)
installed != null -> StoreAchievements.Game.of(installed.sidecar)
else -> null
}
}
if (description.isNotBlank() || achievements != null) Rise(3) {
val about: @Composable (Modifier, Dp) -> Unit = { m, h -> if (description.isNotBlank()) StoreAbout(description, h, m) }
val card: @Composable (Modifier) -> Unit = { m -> if (achievements != null) StoreAchievementsCard(achievements, title, m) }
if (stacked) Column(verticalArrangement = Arrangement.spacedBy(14.dp), modifier = Modifier.fillMaxWidth().padding(top = 14.dp)) {
about(Modifier.fillMaxWidth(), AboutHeight)
card(Modifier.fillMaxWidth())
} else Row(horizontalArrangement = Arrangement.spacedBy(24.dp), modifier = Modifier.fillMaxWidth().padding(top = 14.dp)) {
val density = LocalDensity.current
var cardHeight by remember { mutableStateOf<Dp?>(null) }
about(Modifier.weight(1f), cardHeight ?: AboutHeight)
card(Modifier.width(340.dp).onSizeChanged { cardHeight = with(density) { it.height.toDp() } })
}
}
}
private val AboutHeight = 150.dp
@Composable
private fun StoreAbout(text: String, height: Dp, modifier: Modifier = Modifier) {
val colors = MaterialTheme.colorScheme
val pal = LocalPalette.current
val scroll = rememberScrollState()
val scope = rememberCoroutineScope()
val src = remember { MutableInteractionSource() }
val more = scroll.maxValue > 0
val hot = more && rememberHot(src)
val shape = RoundedCornerShape(12.dp)
Box(
modifier.heightIn(max = height).clip(shape)
.glideBorder(hot, shape, pal.signal, restColor = Color.Transparent)
.then(if (more) Modifier.paneItem("about") else Modifier)
.focusable(enabled = more, interactionSource = src)
.onPreviewKeyEvent { e ->
val step = when (e.nativeKeyEvent.keyCode) {
android.view.KeyEvent.KEYCODE_DPAD_DOWN -> if (scroll.value < scroll.maxValue) 120f else return@onPreviewKeyEvent false
android.view.KeyEvent.KEYCODE_DPAD_UP -> if (scroll.value > 0) -120f else return@onPreviewKeyEvent false
else -> return@onPreviewKeyEvent false
}
if (e.nativeKeyEvent.action == android.view.KeyEvent.ACTION_DOWN) scope.launch { scroll.animateScrollBy(step) }
true
},
) {
Text(text, fontSize = 14.sp, lineHeight = 21.sp, color = colors.onSurfaceVariant, modifier = Modifier.verticalScroll(scroll).padding(horizontal = 10.dp, vertical = 6.dp))
if (scroll.value < scroll.maxValue) Box(
Modifier.align(Alignment.BottomCenter).fillMaxWidth().height(28.dp)
.background(Brush.verticalGradient(listOf(Color.Transparent, colors.background))),
)
}
}
@Composable
private fun UninstallButton(title: String, game: com.droiddeck.launcher.stores.InstalledStoreGame, enabled: Boolean, modifier: Modifier = Modifier, onUninstall: () -> Unit) {
val colors = MaterialTheme.colorScheme
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
val heading = stringResource(R.string.stores_uninstall_title, title)
val size = com.droiddeck.launcher.stores.StoreSizes.onDisk["${game.sidecar.store.id}:${game.sidecar.id}"]
val body = if (size != null && size > 0) stringResource(R.string.stores_uninstall_body_size, formatBytes(size)) else stringResource(R.string.stores_uninstall_body)
val keep = stringResource(R.string.stores_notification_keep)
val remove = stringResource(R.string.stores_uninstall)
Box(Modifier.onGloballyPositioned { placed[0] = it }) {
SecondaryButton(remove, enabled = enabled, modifier = modifier) {
val at = placed[0]?.takeIf { it.isAttached }?.boundsInRoot() ?: return@SecondaryButton
var confirmed = false
StoresMotion.ask(StepAsk(anchor = at, side = StepSide.Right, accent = colors.error, items = 3, pillCorner = 12.dp, maxWidth = 340.dp,
onDismiss = { if (confirmed) onUninstall() }, handle = {
Text(remove, fontSize = 15.sp, fontWeight = FontWeight.SemiBold, color = colors.error, maxLines = 1)
}) {
StepTitle(heading)
Text(body, fontSize = 13.sp, lineHeight = 18.sp, color = colors.onSurfaceVariant, modifier = Modifier.stepItem(1).padding(start = 4.dp, bottom = 12.dp))
Row(horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End), modifier = Modifier.fillMaxWidth().stepItem(2)) {
StepChoice(keep, enabled = open, modifier = Modifier.focusRequester(first)) { StoresMotion.fold() }
StepChoice(remove, danger = true, enabled = open) { confirmed = true; StoresMotion.fold() }
}
})
}
}
}
@Composable
private fun InstallButton(label: String, modifier: Modifier = Modifier, onInstall: () -> Unit) {
val placed = remember { arrayOfNulls<androidx.compose.ui.layout.LayoutCoordinates>(1) }
Box(Modifier.onGloballyPositioned { placed[0] = it }) {
PrimaryButton(label, main = true, modifier = modifier) {
StoresMotion.markInstall(placed[0]?.takeIf { it.isAttached }?.boundsInRoot(), label)
onInstall()
}
}
}
@Composable
private fun XboxStreamButton(item: com.droiddeck.launcher.stores.CatalogItem, main: Boolean = false, modifier: Modifier = Modifier) {
val ctx = LocalContext.current
androidx.compose.runtime.LaunchedEffect(Unit) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.prefetch(ctx) }
val stream = { com.droiddeck.launcher.stores.xbox.XboxCloudGames.stream(ctx, item.id, item.title) }
if (main) PrimaryButton("Stream", main = true, icon = Icons.Filled.PlayArrow, modifier = modifier) { stream() }
else SecondaryButton("Stream", modifier = modifier) { stream() }
}
@Composable
private fun XboxAddToGamesButton(item: com.droiddeck.launcher.stores.CatalogItem) {
val ctx = LocalContext.current
androidx.compose.runtime.LaunchedEffect(Unit) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.load(ctx) }
// Add and Remove replace each other; the controller focus moves to the new button instead of getting lost.
val slot = remember { androidx.compose.ui.focus.FocusRequester() }
val inputMode = androidx.compose.ui.platform.LocalInputModeManager.current
val scope = rememberCoroutineScope()
fun refocus() { if (inputMode.inputMode == androidx.compose.ui.input.InputMode.Keyboard) scope.launch { focusSoon { slot } } }
if (com.droiddeck.launcher.stores.xbox.XboxCloudGames.isAdded(item.id)) {
ActionChip("In Games", ok = true)
SecondaryButton("Remove from Games", compact = true, modifier = Modifier.focusRequester(slot)) {
com.droiddeck.launcher.stores.xbox.XboxCloudGames.remove(ctx, item.id); refocus()
}
} else SecondaryButton("Add to Games", modifier = Modifier.focusRequester(slot)) { com.droiddeck.launcher.stores.xbox.XboxCloudGames.add(ctx, item); refocus() }
}
@@ -498,16 +498,10 @@ internal fun storesSettingsStep(at: Rect, s: androidx.compose.runtime.State<Fron
val st = s.value
val context = androidx.compose.ui.platform.LocalContext.current
SettingsRow(stringResource(R.string.stores_tabs_arrange), null) {
RoundAction(Icons.Filled.SwapHoriz, stringResource(R.string.stores_tabs_arrange), size = 36.dp) {
Box(Modifier.focusRequester(first).focusGroup()) { RoundAction(Icons.Filled.SwapHoriz, stringResource(R.string.stores_tabs_arrange), size = 36.dp) {
StoresMotion.fold()
onArrange()
}
}
SettingsRow(stringResource(R.string.setup_stores_open_on), null, stacked = true) {
Box(Modifier.focusRequester(first).focusGroup()) { SegmentedTabs(
listOf(SessionPrefs.STORES_OPEN_LIBRARY to stringResource(R.string.stores_tab_library), SessionPrefs.STORES_OPEN_STORE to stringResource(R.string.stores_tab_store)),
st.storesOpenTab,
) { a.onStoresOpenTab(it) } }
} }
}
SettingsRow(stringResource(R.string.stores_show_mature), null) {
ToggleSwitch(st.storesShowMature, label = stringResource(R.string.stores_show_mature)) { a.onStoresShowMature(it) }
File diff suppressed because it is too large. Load diff
+35 -9
View File
@@ -1047,8 +1047,6 @@
<string name="games_empty">الألعاب التي تثبّتها في Steam تظهر هنا بعد الجلسة.</string>
<string name="games_play_steam">تشغيل Steam</string>
<string name="games_play_steamvr">تشغيل SteamVR</string>
<string name="games_launch_settings">إعدادات التشغيل</string>
<string name="games_launch">تشغيل</string>
<string name="games_files">ملفات اللعبة</string>
<string name="games_prefix">بادئة Proton</string>
<string name="games_saves_looking">جارٍ البحث عن الحفظ…</string>
@@ -1056,7 +1054,6 @@
<string name="games_saves_folders">%1$d مجلدات حفظ</string>
<string name="games_saves_summary">%1$s · %2$d ملفات · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">إدارة الحفظ</string>
<string name="games_saves">حفظ اللعبة</string>
<string name="games_import_saves">استيراد الحفظ…</string>
<string name="games_export_gamehub">تصدير بصيغة GameHub المضغوطة…</string>
@@ -1068,7 +1065,6 @@
<string name="games_runtime_first_play">تُثبَّت بيئة التشغيل عند أول تشغيل</string>
<string name="games_runtime_update">يتوفر تحديث لبيئة التشغيل</string>
<string name="games_runtime_ready">● بيئة التشغيل جاهزة</string>
<string name="games_last_played">آخر تشغيل %1$s</string>
<string name="games_internal">التخزين الداخلي</string>
<string name="games_added">لعبة مُضافة</string>
<string name="games_custom">لعبة مخصّصة</string>
@@ -1137,7 +1133,7 @@
<string name="second_terminal">الوحدة الطرفية لـ Linux</string>
<string name="second_terminal_not_ready">جلسة Linux ليست جاهزة للوحدة الطرفية بعد.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">الألعاب</string>
<string name="rail_games">المكتبة</string>
<string name="rail_desktop">سطح المكتب</string>
<string name="rail_store">المتجر</string>
<string name="rail_components">المكوّنات</string>
@@ -2031,7 +2027,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">مخصَّص</string>
<string name="setup_stores">المتاجر</string>
<string name="setup_stores_open_on">فتح متجر على</string>
<string name="setup_stores_speed">فئة سرعة التنزيل</string>
<string name="setup_stores_engine">محرك المتاجر</string>
<string name="setup_stores_engine_chip_ready">● المحرك جاهز</string>
@@ -2045,11 +2040,9 @@
<string name="stores_tabs_arrange">ترتيب التبويبات</string>
<string name="stores_tabs_arrange_done">تم</string>
<string name="stores_chip_signed_out">(لم يتم تسجيل الدخول)</string>
<string name="stores_tab_store">المتجر</string>
<string name="stores_tab_library">المكتبة</string>
<string name="stores_tab_all">الكل</string>
<string name="stores_tab_downloads">التنزيلات</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">تسجيل الدخول</string>
<string name="stores_signout_named">%1$s · تسجيل الخروج</string>
<string name="stores_signout_title">تسجيل الخروج من %1$s؟</string>
@@ -2067,7 +2060,6 @@
<string name="stores_shelf_deals">العروض</string>
<string name="stores_shelf_free">ألعاب مجانية</string>
<string name="stores_shelf_trending">الرائج</string>
<string name="stores_shelf_library">مكتبتك</string>
<string name="stores_play">تشغيل</string>
<string name="stores_install">تثبيت</string>
<string name="stores_resume_install">استئناف التثبيت</string>
@@ -3144,4 +3136,38 @@
<string name="spatial_saved_views_hint">يمكن إعادة حساب المشاهدات المحفوظة. ابدأ معايرة جديدة لالتقاطها في نظام إحداثيات التتبع الحالي.</string>
<string name="spatial_model_checksum_error">المجموع الاختباري لنموذج تتبع اليدين غير مطابق</string>
<string name="spatial_opencv_load_error">تعذر تحميل OpenCV</string>
<string name="epic_client_missing">غير مثبَّت</string>
<string name="epic_client_open">فتح</string>
<string name="epic_client_install">تثبيت</string>
<string name="game_tab_about">حول</string>
<string name="game_tab_install">تثبيت</string>
<string name="game_install_location">الموقع</string>
<string name="game_install_folder">مجلد</string>
<string name="game_install_size">الحجم على القرص</string>
<string name="game_install_date">مثبّت</string>
<string name="stores_xbox_update">تحديث / تحقق</string>
<string name="stores_xbox_manage">إدارة المحتوى</string>
<string name="stores_xbox_close_first">أغلق لعبة Xbox هذه قبل تحديثها.</string>
<string name="stores_xbox_content_title">محتوى Xbox</string>
<string name="stores_xbox_content_none">لا توجد حزم محتوى PC مرخّصة لهذه اللعبة.</string>
<string name="stores_xbox_content_update">%1$s · تحديث / تحقق</string>
<string name="stores_xbox_content_install">%1$s · تثبيت</string>
<string name="stores_xbox_choose_content">اختر محتوى PC</string>
<string name="stores_eyebrow_pass">متاحة مع %1$s</string>
<string name="stores_owned_in_library">الألعاب المملوكة في المكتبة</string>
<string name="library_filter_added">مُضافة</string>
<string name="game_tab_launch">التشغيل</string>
<string name="game_tab_saves_cloud">الحفظ والسحابة</string>
<string name="game_launch_shortcut">اختصار Steam</string>
<string name="game_install_version">الإصدار</string>
<string name="game_install_played">آخر لعب</string>
<string name="game_install_access">الوصول</string>
<string name="game_install_access_owned">مملوكة</string>
<string name="game_stream">بث</string>
<string name="library_add">إضافة إلى المكتبة</string>
<string name="library_remove">إزالة من المكتبة</string>
<string name="library_in">في المكتبة</string>
<string name="xbox_filter_show">عرض</string>
<string name="xbox_filter_cloud">سحابية</string>
<string name="stores_xbox_cloud_settings">إعدادات السحابة</string>
</resources>
+35 -9
View File
@@ -1086,8 +1086,6 @@
<string name="games_empty">Los juegos que instales en Steam aparecen aquí después de la sesión.</string>
<string name="games_play_steam">Jugar en Steam</string>
<string name="games_play_steamvr">Jugar en SteamVR</string>
<string name="games_launch_settings">Ajustes de inicio</string>
<string name="games_launch">Iniciar</string>
<string name="games_files">Archivos del juego</string>
<string name="games_prefix">Prefijo de Proton</string>
<string name="games_saves_looking">Buscando partidas guardadas…</string>
@@ -1095,7 +1093,6 @@
<string name="games_saves_folders">%1$d carpetas de guardado</string>
<string name="games_saves_summary">%1$s · %2$d archivos · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Administrar partidas guardadas</string>
<string name="games_saves">Partidas guardadas</string>
<string name="games_import_saves">Importar partidas guardadas…</string>
<string name="games_export_gamehub">Exportar como zip de GameHub…</string>
@@ -1107,7 +1104,6 @@
<string name="games_runtime_first_play">El runtime se instala al jugar</string>
<string name="games_runtime_update">Actualización del runtime disponible</string>
<string name="games_runtime_ready">● Runtime listo</string>
<string name="games_last_played">Última partida %1$s</string>
<string name="games_internal">Almacenamiento interno</string>
<string name="games_added">Juego agregado</string>
<string name="games_custom">Juego personalizado</string>
@@ -1176,7 +1172,7 @@
<string name="second_terminal">Terminal de Linux</string>
<string name="second_terminal_not_ready">La sesión de Linux aún no está lista para un terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Juegos</string>
<string name="rail_games">Biblioteca</string>
<string name="rail_desktop">Escritorio</string>
<string name="rail_store">Tienda</string>
<string name="rail_components">Componentes</string>
@@ -1999,7 +1995,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizados</string>
<string name="setup_stores">Tiendas</string>
<string name="setup_stores_open_on">Abrir una tienda en</string>
<string name="setup_stores_speed">Nivel de velocidad de descarga</string>
<string name="setup_stores_engine">Motor de las tiendas</string>
<string name="setup_stores_engine_chip_ready">● Motor listo</string>
@@ -2013,11 +2008,9 @@
<string name="stores_tabs_arrange">Organizar pestañas</string>
<string name="stores_tabs_arrange_done">Listo</string>
<string name="stores_chip_signed_out">(sin iniciar sesión)</string>
<string name="stores_tab_store">Tienda</string>
<string name="stores_tab_library">Biblioteca</string>
<string name="stores_tab_all">Todos</string>
<string name="stores_tab_downloads">Descargas</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Iniciar sesión</string>
<string name="stores_signout_named">%1$s · cerrar sesión</string>
<string name="stores_signout_title">¿Cerrar sesión en %1$s?</string>
@@ -2035,7 +2028,6 @@
<string name="stores_shelf_deals">Ofertas</string>
<string name="stores_shelf_free">Juegos gratis</string>
<string name="stores_shelf_trending">En tendencia</string>
<string name="stores_shelf_library">Tu biblioteca</string>
<string name="stores_play">Jugar</string>
<string name="stores_install">Instalar</string>
<string name="stores_resume_install">Reanudar instalación</string>
@@ -2994,4 +2986,38 @@
<string name="spatial_saved_views_hint">Las vistas guardadas se pueden recalcular. Inicie una nueva calibración para capturar en el sistema de coordenadas de seguimiento actual.</string>
<string name="spatial_model_checksum_error">La suma de comprobación del modelo de mano no coincide</string>
<string name="spatial_opencv_load_error">OpenCV no se pudo cargar</string>
<string name="epic_client_missing">No instalada</string>
<string name="epic_client_open">Abrir</string>
<string name="epic_client_install">Instalar</string>
<string name="game_tab_about">Acerca de</string>
<string name="game_tab_install">Instalar</string>
<string name="game_install_location">Ubicación</string>
<string name="game_install_folder">Carpeta</string>
<string name="game_install_size">Tamaño en disco</string>
<string name="game_install_date">Instalado</string>
<string name="stores_xbox_update">Actualizar / verificar</string>
<string name="stores_xbox_manage">Administrar contenido</string>
<string name="stores_xbox_close_first">Cierra este juego de Xbox antes de actualizarlo.</string>
<string name="stores_xbox_content_title">Contenido de Xbox</string>
<string name="stores_xbox_content_none">No hay paquetes de contenido de PC con licencia para este juego.</string>
<string name="stores_xbox_content_update">%1$s · Actualizar / verificar</string>
<string name="stores_xbox_content_install">%1$s · Instalar</string>
<string name="stores_xbox_choose_content">Elegir contenido de PC</string>
<string name="stores_eyebrow_pass">disponible con %1$s</string>
<string name="stores_owned_in_library">Juegos comprados en Biblioteca</string>
<string name="library_filter_added">Agregados</string>
<string name="game_tab_launch">Inicio</string>
<string name="game_tab_saves_cloud">Partidas y nube</string>
<string name="game_launch_shortcut">Acceso directo de Steam</string>
<string name="game_install_version">Versión</string>
<string name="game_install_played">Jugado por última vez</string>
<string name="game_install_access">Acceso</string>
<string name="game_install_access_owned">Comprado</string>
<string name="game_stream">Transmitir</string>
<string name="library_add">Agregar a Biblioteca</string>
<string name="library_remove">Quitar de Biblioteca</string>
<string name="library_in">En Biblioteca</string>
<string name="xbox_filter_show">Mostrar</string>
<string name="xbox_filter_cloud">Nube</string>
<string name="stores_xbox_cloud_settings">Configuración de nube</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Игрите, които инсталирате в Steam, се показват тук след сесията.</string>
<string name="games_play_steam">Пускане на Steam</string>
<string name="games_play_steamvr">Пускане на SteamVR</string>
<string name="games_launch_settings">Настройки за стартиране</string>
<string name="games_launch">Стартиране</string>
<string name="games_files">Файлове на играта</string>
<string name="games_prefix">Префикс на Proton</string>
<string name="games_saves_looking">Търсене на записи…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d папки за записи</string>
<string name="games_saves_summary">%1$s · %2$d файла · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Управление на записите</string>
<string name="games_saves">Записи на играта</string>
<string name="games_import_saves">Импортиране на записи…</string>
<string name="games_export_gamehub">Експортиране като GameHub zip…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Средата за изпълнение се инсталира при първото Play</string>
<string name="games_runtime_update">Налична актуализация на средата за изпълнение</string>
<string name="games_runtime_ready">● Средата за изпълнение е готова</string>
<string name="games_last_played">Последна игра %1$s</string>
<string name="games_internal">Вътрешна памет</string>
<string name="games_added">Добавена игра</string>
<string name="games_custom">Персонализирана игра</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Терминал на Linux</string>
<string name="second_terminal_not_ready">Сесията на Linux все още не е готова за терминал.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Игри</string>
<string name="rail_games">Библиотека</string>
<string name="rail_desktop">Работен плот</string>
<string name="rail_store">Магазин</string>
<string name="rail_components">Компоненти</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Персонализирана</string>
<string name="setup_stores">Магазини</string>
<string name="setup_stores_open_on">Отваряне на магазин на</string>
<string name="setup_stores_speed">Скорост на изтегляне</string>
<string name="setup_stores_engine">Ядро на магазините</string>
<string name="setup_stores_engine_chip_ready">● Ядрото е готово</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Подреждане на раздели</string>
<string name="stores_tabs_arrange_done">Готово</string>
<string name="stores_chip_signed_out">(не е вписан)</string>
<string name="stores_tab_store">Магазин</string>
<string name="stores_tab_library">Библиотека</string>
<string name="stores_tab_all">Всички</string>
<string name="stores_tab_downloads">Изтегляния</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Вход</string>
<string name="stores_signout_named">%1$s · изход</string>
<string name="stores_signout_title">Изход от %1$s?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Промоции</string>
<string name="stores_shelf_free">Безплатни игри</string>
<string name="stores_shelf_trending">Актуални</string>
<string name="stores_shelf_library">Твоята библиотека</string>
<string name="stores_play">Играй</string>
<string name="stores_install">Инсталирай</string>
<string name="stores_resume_install">Продължи инсталацията</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Запазените изгледи могат да бъдат преизчислени. Започнете ново калибриране за заснемане в текущата координатна система за проследяване.</string>
<string name="spatial_model_checksum_error">Несъответствие на контролната сума на модела на ръката</string>
<string name="spatial_opencv_load_error">OpenCV не можа да се зареди</string>
<string name="epic_client_missing">Не е инсталирано</string>
<string name="epic_client_open">Отвори</string>
<string name="epic_client_install">Инсталиране</string>
<string name="game_tab_about">За приложението</string>
<string name="game_tab_install">Инсталиране</string>
<string name="game_install_location">Местоположение</string>
<string name="game_install_folder">Папка</string>
<string name="game_install_size">Размер на диска</string>
<string name="game_install_date">Инсталиран</string>
<string name="stores_xbox_update">Обнови / провери</string>
<string name="stores_xbox_manage">Управление на съдържанието</string>
<string name="stores_xbox_close_first">Затворете тази Xbox игра, преди да я обновите.</string>
<string name="stores_xbox_content_title">Xbox съдържание</string>
<string name="stores_xbox_content_none">Няма лицензирани пакети с PC съдържание за тази игра.</string>
<string name="stores_xbox_content_update">%1$s · Обнови / провери</string>
<string name="stores_xbox_content_install">%1$s · Инсталирай</string>
<string name="stores_xbox_choose_content">Изберете PC съдържание</string>
<string name="stores_eyebrow_pass">налична с %1$s</string>
<string name="stores_owned_in_library">Притежавани игри в Библиотека</string>
<string name="library_filter_added">Добавени</string>
<string name="game_tab_launch">Стартиране</string>
<string name="game_tab_saves_cloud">Записи и облак</string>
<string name="game_launch_shortcut">Пряк път в Steam</string>
<string name="game_install_version">Версия</string>
<string name="game_install_played">Последно игране</string>
<string name="game_install_access">Достъп</string>
<string name="game_install_access_owned">Притежавана</string>
<string name="game_stream">Стрийминг</string>
<string name="library_add">Добави в Библиотека</string>
<string name="library_remove">Премахни от Библиотека</string>
<string name="library_in">В Библиотека</string>
<string name="xbox_filter_show">Показване</string>
<string name="xbox_filter_cloud">Облак</string>
<string name="stores_xbox_cloud_settings">Настройки на облака</string>
</resources>
+35 -9
View File
@@ -1035,8 +1035,6 @@
<string name="games_empty">Hry, které nainstalujete ve Steamu, se zde zobrazí po relaci.</string>
<string name="games_play_steam">Hrát ve Steamu</string>
<string name="games_play_steamvr">Hrát ve SteamVR</string>
<string name="games_launch_settings">Nastavení spuštění</string>
<string name="games_launch">Spustit</string>
<string name="games_files">Soubory hry</string>
<string name="games_prefix">Prefix Protonu</string>
<string name="games_saves_looking">Hledání uložených pozic…</string>
@@ -1044,7 +1042,6 @@
<string name="games_saves_folders">%1$d složek s uloženými pozicemi</string>
<string name="games_saves_summary">%1$s · %2$d souborů · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Správa uložených pozic</string>
<string name="games_saves">Uložené pozice hry</string>
<string name="games_import_saves">Importovat uložené pozice…</string>
<string name="games_export_gamehub">Exportovat jako GameHub zip…</string>
@@ -1056,7 +1053,6 @@
<string name="games_runtime_first_play">Runtime se nainstaluje při prvním spuštění hry</string>
<string name="games_runtime_update">K dispozici aktualizace runtime</string>
<string name="games_runtime_ready">● Runtime připraven</string>
<string name="games_last_played">Poslední hraní %1$s</string>
<string name="games_internal">Interní úložiště</string>
<string name="games_added">Přidaná hra</string>
<string name="games_custom">Vlastní hra</string>
@@ -1125,7 +1121,7 @@
<string name="second_terminal">Terminál Linuxu</string>
<string name="second_terminal_not_ready">Relace Linuxu ještě není připravena na terminál.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Hry</string>
<string name="rail_games">Knihovna</string>
<string name="rail_desktop">Plocha</string>
<string name="rail_store">Obchod</string>
<string name="rail_components">Komponenty</string>
@@ -1989,7 +1985,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Vlastní</string>
<string name="setup_stores">Obchody</string>
<string name="setup_stores_open_on">Otevřít obchod na</string>
<string name="setup_stores_speed">Úroveň rychlosti stahování</string>
<string name="setup_stores_engine">Jádro obchodů</string>
<string name="setup_stores_engine_chip_ready">● Jádro je připraveno</string>
@@ -2003,11 +1998,9 @@
<string name="stores_tabs_arrange">Uspořádat karty</string>
<string name="stores_tabs_arrange_done">Hotovo</string>
<string name="stores_chip_signed_out">(nepřihlášen)</string>
<string name="stores_tab_store">Obchod</string>
<string name="stores_tab_library">Knihovna</string>
<string name="stores_tab_all">Vše</string>
<string name="stores_tab_downloads">Stahování</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Přihlásit se</string>
<string name="stores_signout_named">%1$s · odhlásit</string>
<string name="stores_signout_title">Odhlásit se z %1$s?</string>
@@ -2025,7 +2018,6 @@
<string name="stores_shelf_deals">Nabídky</string>
<string name="stores_shelf_free">Hry zdarma</string>
<string name="stores_shelf_trending">Populární</string>
<string name="stores_shelf_library">Vaše knihovna</string>
<string name="stores_play">Hrát</string>
<string name="stores_install">Instalovat</string>
<string name="stores_resume_install">Pokračovat v instalaci</string>
@@ -3076,4 +3068,38 @@
<string name="spatial_saved_views_hint">Uložená zobrazení lze přepočítat. Spusťte novou kalibraci pro zachycení v aktuálním sledovacím souřadnicovém systému.</string>
<string name="spatial_model_checksum_error">Neshoda kontrolního součtu modelu ruky</string>
<string name="spatial_opencv_load_error">OpenCV se nepodařilo načíst</string>
<string name="epic_client_missing">Není nainstalováno</string>
<string name="epic_client_open">Otevřít</string>
<string name="epic_client_install">Instalovat</string>
<string name="game_tab_about">O aplikaci</string>
<string name="game_tab_install">Instalovat</string>
<string name="game_install_location">Umístění</string>
<string name="game_install_folder">Složka</string>
<string name="game_install_size">Velikost na disku</string>
<string name="game_install_date">Nainstalováno</string>
<string name="stores_xbox_update">Aktualizovat / ověřit</string>
<string name="stores_xbox_manage">Spravovat obsah</string>
<string name="stores_xbox_close_first">Před aktualizací tuto hru pro Xbox zavřete.</string>
<string name="stores_xbox_content_title">Obsah Xbox</string>
<string name="stores_xbox_content_none">Pro tuto hru nejsou k dispozici žádné licencované balíčky obsahu pro PC.</string>
<string name="stores_xbox_content_update">%1$s · Aktualizovat / ověřit</string>
<string name="stores_xbox_content_install">%1$s · Instalovat</string>
<string name="stores_xbox_choose_content">Vyberte obsah pro PC</string>
<string name="stores_eyebrow_pass">dostupné s %1$s</string>
<string name="stores_owned_in_library">Vlastněné hry v knihovně</string>
<string name="library_filter_added">Přidané</string>
<string name="game_tab_launch">Spuštění</string>
<string name="game_tab_saves_cloud">Uložené hry a cloud</string>
<string name="game_launch_shortcut">Zástupce ve Steamu</string>
<string name="game_install_version">Verze</string>
<string name="game_install_played">Naposledy hráno</string>
<string name="game_install_access">Přístup</string>
<string name="game_install_access_owned">Vlastněno</string>
<string name="game_stream">Streamovat</string>
<string name="library_add">Přidat do knihovny</string>
<string name="library_remove">Odebrat z knihovny</string>
<string name="library_in">V knihovně</string>
<string name="xbox_filter_show">Zobrazit</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Nastavení cloudu</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Spil, du installerer i Steam, vises her efter sessionen.</string>
<string name="games_play_steam">Spil Steam</string>
<string name="games_play_steamvr">Spil SteamVR</string>
<string name="games_launch_settings">Startindstillinger</string>
<string name="games_launch">Start</string>
<string name="games_files">Spilfiler</string>
<string name="games_prefix">Proton-prefix</string>
<string name="games_saves_looking">Søger efter gemte spil…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d savemapper</string>
<string name="games_saves_summary">%1$s · %2$d filer · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Administrer gemte spil</string>
<string name="games_saves">Gemte spil</string>
<string name="games_import_saves">Importer gemte spil…</string>
<string name="games_export_gamehub">Eksporter som GameHub-zip…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Runtime installeres ved første Spil</string>
<string name="games_runtime_update">Runtimeopdatering tilgængelig</string>
<string name="games_runtime_ready">● Runtime klar</string>
<string name="games_last_played">Sidst spillet %1$s</string>
<string name="games_internal">Internt lager</string>
<string name="games_added">Tilføjet spil</string>
<string name="games_custom">Brugerdefineret spil</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux-terminal</string>
<string name="second_terminal_not_ready">Linux-sessionen er ikke klar til en terminal endnu.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Spil</string>
<string name="rail_games">Bibliotek</string>
<string name="rail_desktop">Skrivebord</string>
<string name="rail_store">Butik</string>
<string name="rail_components">Komponenter</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Brugerdefineret</string>
<string name="setup_stores">Butikker</string>
<string name="setup_stores_open_on">Åbn en butik på</string>
<string name="setup_stores_speed">Downloadhastighed</string>
<string name="setup_stores_engine">Butiksmotor</string>
<string name="setup_stores_engine_chip_ready">● Motor klar</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Arranger faner</string>
<string name="stores_tabs_arrange_done">Færdig</string>
<string name="stores_chip_signed_out">(ikke logget ind)</string>
<string name="stores_tab_store">Butik</string>
<string name="stores_tab_library">Bibliotek</string>
<string name="stores_tab_all">Alle</string>
<string name="stores_tab_downloads">Downloads</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Log ind</string>
<string name="stores_signout_named">%1$s · log ud</string>
<string name="stores_signout_title">Log ud af %1$s?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Tilbud</string>
<string name="stores_shelf_free">Gratis spil</string>
<string name="stores_shelf_trending">Populært</string>
<string name="stores_shelf_library">Dit bibliotek</string>
<string name="stores_play">Spil</string>
<string name="stores_install">Installer</string>
<string name="stores_resume_install">Genoptag installation</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Gemte visninger kan genberegnes. Start en ny kalibrering for at fange i det aktuelle sporingskoordinatsystem.</string>
<string name="spatial_model_checksum_error">Håndmodel kontrolsum uoverensstemmelse</string>
<string name="spatial_opencv_load_error">OpenCV kunne ikke indlæse</string>
<string name="epic_client_missing">Ikke installeret</string>
<string name="epic_client_open">Åbn</string>
<string name="epic_client_install">Installer</string>
<string name="game_tab_about">Om</string>
<string name="game_tab_install">Installer</string>
<string name="game_install_location">Placering</string>
<string name="game_install_folder">Mappe</string>
<string name="game_install_size">Størrelse på disk</string>
<string name="game_install_date">Installeret</string>
<string name="stores_xbox_update">Opdater / bekræft</string>
<string name="stores_xbox_manage">Administrer indhold</string>
<string name="stores_xbox_close_first">Luk dette Xbox-spil, før du opdaterer det.</string>
<string name="stores_xbox_content_title">Xbox-indhold</string>
<string name="stores_xbox_content_none">Der er ingen licenserede pc-indholdspakker til dette spil.</string>
<string name="stores_xbox_content_update">%1$s · Opdater / bekræft</string>
<string name="stores_xbox_content_install">%1$s · Installer</string>
<string name="stores_xbox_choose_content">Vælg pc-indhold</string>
<string name="stores_eyebrow_pass">tilgængelig med %1$s</string>
<string name="stores_owned_in_library">Ejede spil i Bibliotek</string>
<string name="library_filter_added">Tilføjet</string>
<string name="game_tab_launch">Start</string>
<string name="game_tab_saves_cloud">Gemte spil og sky</string>
<string name="game_launch_shortcut">Steam-genvej</string>
<string name="game_install_version">Version</string>
<string name="game_install_played">Sidst spillet</string>
<string name="game_install_access">Adgang</string>
<string name="game_install_access_owned">Ejet</string>
<string name="game_stream">Stream</string>
<string name="library_add">Føj til Bibliotek</string>
<string name="library_remove">Fjern fra Bibliotek</string>
<string name="library_in">I Bibliotek</string>
<string name="xbox_filter_show">Vis</string>
<string name="xbox_filter_cloud">Sky</string>
<string name="stores_xbox_cloud_settings">Skyindstillinger</string>
</resources>
+35 -9
View File
@@ -1015,8 +1015,6 @@
<string name="games_empty">Spiele, die du in Steam installierst, erscheinen hier nach der Sitzung.</string>
<string name="games_play_steam">Mit Steam spielen</string>
<string name="games_play_steamvr">Mit SteamVR spielen</string>
<string name="games_launch_settings">Starteinstellungen</string>
<string name="games_launch">Starten</string>
<string name="games_files">Spieldateien</string>
<string name="games_prefix">Proton-Präfix</string>
<string name="games_saves_looking">Suche nach Spielständen…</string>
@@ -1024,7 +1022,6 @@
<string name="games_saves_folders">%1$d Spielstandordner</string>
<string name="games_saves_summary">%1$s · %2$d Dateien · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Spielstände verwalten</string>
<string name="games_saves">Spielstände</string>
<string name="games_import_saves">Spielstände importieren…</string>
<string name="games_export_gamehub">Als GameHub-ZIP exportieren…</string>
@@ -1036,7 +1033,6 @@
<string name="games_runtime_first_play">Laufzeit wird beim ersten „Spielen“ installiert</string>
<string name="games_runtime_update">Laufzeit-Update verfügbar</string>
<string name="games_runtime_ready">● Laufzeit bereit</string>
<string name="games_last_played">Zuletzt gespielt: %1$s</string>
<string name="games_internal">Interner Speicher</string>
<string name="games_added">Hinzugefügtes Spiel</string>
<string name="games_custom">Eigenes Spiel</string>
@@ -1105,7 +1101,7 @@
<string name="second_terminal">Linux-Terminal</string>
<string name="second_terminal_not_ready">Die Linux-Sitzung ist noch nicht bereit für ein Terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Spiele</string>
<string name="rail_games">Bibliothek</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Store</string>
<string name="rail_components">Komponenten</string>
@@ -1939,7 +1935,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Eigene</string>
<string name="setup_stores">Stores</string>
<string name="setup_stores_open_on">Store öffnen auf</string>
<string name="setup_stores_speed">Download-Tempo</string>
<string name="setup_stores_engine">Store-Engine</string>
<string name="setup_stores_engine_chip_ready">● Engine bereit</string>
@@ -1953,11 +1948,9 @@
<string name="stores_tabs_arrange">Tabs anordnen</string>
<string name="stores_tabs_arrange_done">Fertig</string>
<string name="stores_chip_signed_out">(nicht angemeldet)</string>
<string name="stores_tab_store">Store</string>
<string name="stores_tab_library">Bibliothek</string>
<string name="stores_tab_all">Alle</string>
<string name="stores_tab_downloads">Downloads</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Anmelden</string>
<string name="stores_signout_named">%1$s · abmelden</string>
<string name="stores_signout_title">Von %1$s abmelden?</string>
@@ -1975,7 +1968,6 @@
<string name="stores_shelf_deals">Angebote</string>
<string name="stores_shelf_free">Kostenlose Spiele</string>
<string name="stores_shelf_trending">Im Trend</string>
<string name="stores_shelf_library">Deine Bibliothek</string>
<string name="stores_play">Spielen</string>
<string name="stores_install">Installieren</string>
<string name="stores_resume_install">Installation fortsetzen</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Gespeicherte Ansichten können neu berechnet werden. Starten Sie eine neue Kalibrierung zur Erfassung im aktuellen Tracking-Koordinatensystem.</string>
<string name="spatial_model_checksum_error">Die Prüfsumme des Handmodells stimmt nicht überein</string>
<string name="spatial_opencv_load_error">OpenCV konnte nicht geladen werden</string>
<string name="epic_client_missing">Nicht installiert</string>
<string name="epic_client_open">Öffnen</string>
<string name="epic_client_install">Installieren</string>
<string name="game_tab_about">Über</string>
<string name="game_tab_install">Installieren</string>
<string name="game_install_location">Speicherort</string>
<string name="game_install_folder">Ordner</string>
<string name="game_install_size">Speicherbedarf</string>
<string name="game_install_date">Installiert</string>
<string name="stores_xbox_update">Aktualisieren / prüfen</string>
<string name="stores_xbox_manage">Inhalte verwalten</string>
<string name="stores_xbox_close_first">Schließe dieses Xbox-Spiel, bevor du es aktualisierst.</string>
<string name="stores_xbox_content_title">Xbox-Inhalte</string>
<string name="stores_xbox_content_none">Für dieses Spiel gibt es keine lizenzierten PC-Inhaltspakete.</string>
<string name="stores_xbox_content_update">%1$s · Aktualisieren / prüfen</string>
<string name="stores_xbox_content_install">%1$s · Installieren</string>
<string name="stores_xbox_choose_content">PC-Inhalt wählen</string>
<string name="stores_eyebrow_pass">verfügbar mit %1$s</string>
<string name="stores_owned_in_library">Eigene Spiele in der Bibliothek</string>
<string name="library_filter_added">Hinzugefügt</string>
<string name="game_tab_launch">Start</string>
<string name="game_tab_saves_cloud">Spielstände &amp; Cloud</string>
<string name="game_launch_shortcut">Steam-Verknüpfung</string>
<string name="game_install_version">Version</string>
<string name="game_install_played">Zuletzt gespielt</string>
<string name="game_install_access">Zugang</string>
<string name="game_install_access_owned">Gekauft</string>
<string name="game_stream">Streamen</string>
<string name="library_add">Zur Bibliothek hinzufügen</string>
<string name="library_remove">Aus der Bibliothek entfernen</string>
<string name="library_in">In der Bibliothek</string>
<string name="xbox_filter_show">Anzeigen</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Cloud-Einstellungen</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Τα παιχνίδια που εγκαθιστάτε στο Steam εμφανίζονται εδώ μετά τη συνεδρία.</string>
<string name="games_play_steam">Αναπαραγωγή Steam</string>
<string name="games_play_steamvr">Αναπαραγωγή SteamVR</string>
<string name="games_launch_settings">Ρυθμίσεις εκκίνησης</string>
<string name="games_launch">Εκκίνηση</string>
<string name="games_files">Αρχεία παιχνιδιού</string>
<string name="games_prefix">Prefix Proton</string>
<string name="games_saves_looking">Αναζήτηση αποθηκευμένων αρχείων…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d φάκελοι αποθήκευσης</string>
<string name="games_saves_summary">%1$s · %2$d αρχεία · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Διαχείριση αποθηκευμένων</string>
<string name="games_saves">Αποθηκευμένα παιχνιδιού</string>
<string name="games_import_saves">Εισαγωγή αποθηκευμένων…</string>
<string name="games_export_gamehub">Εξαγωγή ως zip GameHub…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Το runtime εγκαθίσταται στο πρώτο Play</string>
<string name="games_runtime_update">Υπάρχει ενημέρωση runtime</string>
<string name="games_runtime_ready">● Το runtime είναι έτοιμο</string>
<string name="games_last_played">Παίχτηκε τελευταία φορά %1$s</string>
<string name="games_internal">Εσωτερική αποθήκευση</string>
<string name="games_added">Προστέθηκε παιχνίδι</string>
<string name="games_custom">Προσαρμοσμένο παιχνίδι</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Τερματικό Linux</string>
<string name="second_terminal_not_ready">Η συνεδρία Linux δεν είναι ακόμα έτοιμη για τερματικό.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Παιχνίδια</string>
<string name="rail_games">Βιβλιοθήκη</string>
<string name="rail_desktop">Επιφάνεια</string>
<string name="rail_store">Κατάστημα</string>
<string name="rail_components">Στοιχεία</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Προσαρμοσμένο</string>
<string name="setup_stores">Καταστήματα</string>
<string name="setup_stores_open_on">Άνοιγμα καταστήματος σε</string>
<string name="setup_stores_speed">Επίπεδο ταχύτητας λήψης</string>
<string name="setup_stores_engine">Μηχανή καταστημάτων</string>
<string name="setup_stores_engine_chip_ready">● Η μηχανή είναι έτοιμη</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Τακτοποίηση καρτελών</string>
<string name="stores_tabs_arrange_done">Έγινε</string>
<string name="stores_chip_signed_out">(μη συνδεδεμένος)</string>
<string name="stores_tab_store">Κατάστημα</string>
<string name="stores_tab_library">Βιβλιοθήκη</string>
<string name="stores_tab_all">Όλα</string>
<string name="stores_tab_downloads">Λήψεις</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Σύνδεση</string>
<string name="stores_signout_named">%1$s · αποσύνδεση</string>
<string name="stores_signout_title">Αποσύνδεση από %1$s;</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Προσφορές</string>
<string name="stores_shelf_free">Δωρεάν παιχνίδια</string>
<string name="stores_shelf_trending">Σε άνοδο</string>
<string name="stores_shelf_library">Η βιβλιοθήκη σας</string>
<string name="stores_play">Αναπαραγωγή</string>
<string name="stores_install">Εγκατάσταση</string>
<string name="stores_resume_install">Συνέχιση εγκατάστασης</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Οι αποθηκευμένες προβολές μπορούν να υπολογιστούν εκ νέου. Ξεκινήστε μια νέα βαθμονόμηση για αποτύπωση στο τρέχον σύστημα συντεταγμένων παρακολούθησης.</string>
<string name="spatial_model_checksum_error">Αναντιστοιχία αθροίσματος ελέγχου μοντέλου χεριού</string>
<string name="spatial_opencv_load_error">Δεν ήταν δυνατή η φόρτωση του OpenCV</string>
<string name="epic_client_missing">Δεν είναι εγκατεστημένο</string>
<string name="epic_client_open">Άνοιγμα</string>
<string name="epic_client_install">Εγκατάσταση</string>
<string name="game_tab_about">Σχετικά</string>
<string name="game_tab_install">Εγκατάσταση</string>
<string name="game_install_location">Τοποθεσία</string>
<string name="game_install_folder">Φάκελος</string>
<string name="game_install_size">Μέγεθος στον δίσκο</string>
<string name="game_install_date">Εγκατεστημένο</string>
<string name="stores_xbox_update">Ενημέρωση / επαλήθευση</string>
<string name="stores_xbox_manage">Διαχείριση περιεχομένου</string>
<string name="stores_xbox_close_first">Κλείστε αυτό το παιχνίδι Xbox πριν το ενημερώσετε.</string>
<string name="stores_xbox_content_title">Περιεχόμενο Xbox</string>
<string name="stores_xbox_content_none">Δεν υπάρχουν αδειοδοτημένα πακέτα περιεχομένου PC για αυτό το παιχνίδι.</string>
<string name="stores_xbox_content_update">%1$s · Ενημέρωση / επαλήθευση</string>
<string name="stores_xbox_content_install">%1$s · Εγκατάσταση</string>
<string name="stores_xbox_choose_content">Επιλογή περιεχομένου PC</string>
<string name="stores_eyebrow_pass">διαθέσιμο με %1$s</string>
<string name="stores_owned_in_library">Παιχνίδια σας στη Βιβλιοθήκη</string>
<string name="library_filter_added">Προστέθηκαν</string>
<string name="game_tab_launch">Εκκίνηση</string>
<string name="game_tab_saves_cloud">Αποθηκεύσεις και cloud</string>
<string name="game_launch_shortcut">Συντόμευση Steam</string>
<string name="game_install_version">Έκδοση</string>
<string name="game_install_played">Τελευταίο παιχνίδι</string>
<string name="game_install_access">Πρόσβαση</string>
<string name="game_install_access_owned">Δικό σας</string>
<string name="game_stream">Ροή</string>
<string name="library_add">Προσθήκη στη Βιβλιοθήκη</string>
<string name="library_remove">Αφαίρεση από τη Βιβλιοθήκη</string>
<string name="library_in">Στη Βιβλιοθήκη</string>
<string name="xbox_filter_show">Εμφάνιση</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Ρυθμίσεις cloud</string>
</resources>
+35 -9
View File
@@ -1031,8 +1031,6 @@
<string name="games_empty">Los juegos que instales en Steam aparecen aquí después de la sesión.</string>
<string name="games_play_steam">Jugar en Steam</string>
<string name="games_play_steamvr">Jugar en SteamVR</string>
<string name="games_launch_settings">Ajustes de inicio</string>
<string name="games_launch">Iniciar</string>
<string name="games_files">Archivos del juego</string>
<string name="games_prefix">Prefijo de Proton</string>
<string name="games_saves_looking">Buscando partidas guardadas…</string>
@@ -1040,7 +1038,6 @@
<string name="games_saves_folders">%1$d carpetas de guardado</string>
<string name="games_saves_summary">%1$s · %2$d archivos · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Gestionar partidas guardadas</string>
<string name="games_saves">Partidas guardadas</string>
<string name="games_import_saves">Importar partidas guardadas…</string>
<string name="games_export_gamehub">Exportar como zip de GameHub…</string>
@@ -1052,7 +1049,6 @@
<string name="games_runtime_first_play">El runtime se instala al jugar</string>
<string name="games_runtime_update">Actualización del runtime disponible</string>
<string name="games_runtime_ready">● Runtime listo</string>
<string name="games_last_played">Última partida %1$s</string>
<string name="games_internal">Almacenamiento interno</string>
<string name="games_added">Juego añadido</string>
<string name="games_custom">Juego personalizado</string>
@@ -1121,7 +1117,7 @@
<string name="second_terminal">Terminal de Linux</string>
<string name="second_terminal_not_ready">La sesión de Linux aún no está lista para un terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Juegos</string>
<string name="rail_games">Biblioteca</string>
<string name="rail_desktop">Escritorio</string>
<string name="rail_store">Tienda</string>
<string name="rail_components">Componentes</string>
@@ -1970,7 +1966,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizados</string>
<string name="setup_stores">Tiendas</string>
<string name="setup_stores_open_on">Abrir una tienda en</string>
<string name="setup_stores_speed">Nivel de velocidad de descarga</string>
<string name="setup_stores_engine">Motor de las tiendas</string>
<string name="setup_stores_engine_chip_ready">● Motor listo</string>
@@ -1984,11 +1979,9 @@
<string name="stores_tabs_arrange">Organizar pestañas</string>
<string name="stores_tabs_arrange_done">Listo</string>
<string name="stores_chip_signed_out">(sin iniciar sesión)</string>
<string name="stores_tab_store">Tienda</string>
<string name="stores_tab_library">Biblioteca</string>
<string name="stores_tab_all">Todos</string>
<string name="stores_tab_downloads">Descargas</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Iniciar sesión</string>
<string name="stores_signout_named">%1$s · cerrar sesión</string>
<string name="stores_signout_title">¿Cerrar sesión en %1$s?</string>
@@ -2006,7 +1999,6 @@
<string name="stores_shelf_deals">Ofertas</string>
<string name="stores_shelf_free">Juegos gratis</string>
<string name="stores_shelf_trending">En tendencia</string>
<string name="stores_shelf_library">Tu biblioteca</string>
<string name="stores_play">Jugar</string>
<string name="stores_install">Instalar</string>
<string name="stores_resume_install">Reanudar instalación</string>
@@ -3044,4 +3036,38 @@
<string name="spatial_saved_views_hint">Las vistas guardadas se pueden recalcular. Inicie una nueva calibración para capturar en el sistema de coordenadas de seguimiento actual.</string>
<string name="spatial_model_checksum_error">La suma de comprobación del modelo de mano no coincide</string>
<string name="spatial_opencv_load_error">OpenCV no se pudo cargar</string>
<string name="epic_client_missing">No instalada</string>
<string name="epic_client_open">Abrir</string>
<string name="epic_client_install">Instalar</string>
<string name="game_tab_about">Acerca de</string>
<string name="game_tab_install">Instalar</string>
<string name="game_install_location">Ubicación</string>
<string name="game_install_folder">Carpeta</string>
<string name="game_install_size">Tamaño en disco</string>
<string name="game_install_date">Instalado</string>
<string name="stores_xbox_update">Actualizar / verificar</string>
<string name="stores_xbox_manage">Gestionar contenido</string>
<string name="stores_xbox_close_first">Cierra este juego de Xbox antes de actualizarlo.</string>
<string name="stores_xbox_content_title">Contenido de Xbox</string>
<string name="stores_xbox_content_none">No hay paquetes de contenido de PC con licencia para este juego.</string>
<string name="stores_xbox_content_update">%1$s · Actualizar / verificar</string>
<string name="stores_xbox_content_install">%1$s · Instalar</string>
<string name="stores_xbox_choose_content">Elegir contenido de PC</string>
<string name="stores_eyebrow_pass">disponible con %1$s</string>
<string name="stores_owned_in_library">Juegos comprados en la Biblioteca</string>
<string name="library_filter_added">Añadidos</string>
<string name="game_tab_launch">Inicio</string>
<string name="game_tab_saves_cloud">Partidas y nube</string>
<string name="game_launch_shortcut">Acceso directo de Steam</string>
<string name="game_install_version">Versión</string>
<string name="game_install_played">Jugado por última vez</string>
<string name="game_install_access">Acceso</string>
<string name="game_install_access_owned">Comprado</string>
<string name="game_stream">Transmitir</string>
<string name="library_add">Añadir a la Biblioteca</string>
<string name="library_remove">Quitar de la Biblioteca</string>
<string name="library_in">En la Biblioteca</string>
<string name="xbox_filter_show">Mostrar</string>
<string name="xbox_filter_cloud">Nube</string>
<string name="stores_xbox_cloud_settings">Ajustes de nube</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Steamissa asentamasi pelit näkyvät tässä istunnon jälkeen.</string>
<string name="games_play_steam">Pelaa Steamissa</string>
<string name="games_play_steamvr">Pelaa SteamVR:ssä</string>
<string name="games_launch_settings">Käynnistysasetukset</string>
<string name="games_launch">Käynnistä</string>
<string name="games_files">Pelitiedostot</string>
<string name="games_prefix">Proton-prefix</string>
<string name="games_saves_looking">Etsitään tallennuksia…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d tallennuskansiota</string>
<string name="games_saves_summary">%1$s · %2$d tiedostoa · %3$s</string>
<string name="games_kb">%1$d kt</string>
<string name="games_manage_saves">Hallitse tallennuksia</string>
<string name="games_saves">Pelitallennukset</string>
<string name="games_import_saves">Tuo tallennuksia…</string>
<string name="games_export_gamehub">Vie GameHub-zipinä…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Ajoympäristö asentuu ensimmäisellä Pelaa-painalluksella</string>
<string name="games_runtime_update">Ajoympäristön päivitys saatavilla</string>
<string name="games_runtime_ready">● Ajoympäristö valmis</string>
<string name="games_last_played">Viimeksi pelattu %1$s</string>
<string name="games_internal">Sisäinen tallennustila</string>
<string name="games_added">Lisätty peli</string>
<string name="games_custom">Mukautettu peli</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux-pääte</string>
<string name="second_terminal_not_ready">Linux-istunto ei ole vielä valmis päätteelle.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Pelit</string>
<string name="rail_games">Kirjasto</string>
<string name="rail_desktop">Työpöytä</string>
<string name="rail_store">Kauppa</string>
<string name="rail_components">Komponentit</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Mukautettu</string>
<string name="setup_stores">Kaupat</string>
<string name="setup_stores_open_on">Avaa kauppa näytöllä</string>
<string name="setup_stores_speed">Latausnopeuden taso</string>
<string name="setup_stores_engine">Kauppojen moottori</string>
<string name="setup_stores_engine_chip_ready">● Moottori valmis</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Järjestä välilehdet</string>
<string name="stores_tabs_arrange_done">Valmis</string>
<string name="stores_chip_signed_out">(ei kirjautunut)</string>
<string name="stores_tab_store">Kauppa</string>
<string name="stores_tab_library">Kirjasto</string>
<string name="stores_tab_all">Kaikki</string>
<string name="stores_tab_downloads">Lataukset</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Kirjaudu sisään</string>
<string name="stores_signout_named">%1$s · kirjaudu ulos</string>
<string name="stores_signout_title">Kirjaudutaanko ulos %1$s:stä?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Tarjoukset</string>
<string name="stores_shelf_free">Ilmaiset pelit</string>
<string name="stores_shelf_trending">Nousussa</string>
<string name="stores_shelf_library">Kirjastosi</string>
<string name="stores_play">Pelaa</string>
<string name="stores_install">Asenna</string>
<string name="stores_resume_install">Jatka asennusta</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Tallennetut näkymät voidaan laskea uudelleen. Aloita uusi kalibrointi kaapataksesi nykyiseen seurantakoordinaattijärjestelmään.</string>
<string name="spatial_model_checksum_error">Käsimallin tarkistussumman epäsuhta</string>
<string name="spatial_opencv_load_error">OpenCV:tä ei voitu ladata</string>
<string name="epic_client_missing">Ei asennettu</string>
<string name="epic_client_open">Avaa</string>
<string name="epic_client_install">Asenna</string>
<string name="game_tab_about">Tietoja</string>
<string name="game_tab_install">Asenna</string>
<string name="game_install_location">Sijainti</string>
<string name="game_install_folder">Kansio</string>
<string name="game_install_size">Koko levyllä</string>
<string name="game_install_date">Asennettu</string>
<string name="stores_xbox_update">Päivitä / tarkista</string>
<string name="stores_xbox_manage">Hallitse sisältöä</string>
<string name="stores_xbox_close_first">Sulje tämä Xbox-peli ennen päivittämistä.</string>
<string name="stores_xbox_content_title">Xbox-sisältö</string>
<string name="stores_xbox_content_none">Tälle pelille ei ole lisensoituja PC-sisältöpaketteja.</string>
<string name="stores_xbox_content_update">%1$s · Päivitä / tarkista</string>
<string name="stores_xbox_content_install">%1$s · Asenna</string>
<string name="stores_xbox_choose_content">Valitse PC-sisältö</string>
<string name="stores_eyebrow_pass">saatavilla palvelussa %1$s</string>
<string name="stores_owned_in_library">Omistetut pelit kirjastossa</string>
<string name="library_filter_added">Lisätyt</string>
<string name="game_tab_launch">Käynnistys</string>
<string name="game_tab_saves_cloud">Tallennukset ja pilvi</string>
<string name="game_launch_shortcut">Steam-pikakuvake</string>
<string name="game_install_version">Versio</string>
<string name="game_install_played">Viimeksi pelattu</string>
<string name="game_install_access">Käyttöoikeus</string>
<string name="game_install_access_owned">Omistettu</string>
<string name="game_stream">Striimaa</string>
<string name="library_add">Lisää kirjastoon</string>
<string name="library_remove">Poista kirjastosta</string>
<string name="library_in">Kirjastossa</string>
<string name="xbox_filter_show">Näytä</string>
<string name="xbox_filter_cloud">Pilvi</string>
<string name="stores_xbox_cloud_settings">Pilviasetukset</string>
</resources>
+35 -9
View File
@@ -1031,8 +1031,6 @@
<string name="games_empty">Les jeux installés sur Steam apparaîtront ici après la fin de la session.</string>
<string name="games_play_steam">Lancer Steam</string>
<string name="games_play_steamvr">Lancer SteamVR</string>
<string name="games_launch_settings">Paramètres de lancement</string>
<string name="games_launch">Lancer</string>
<string name="games_files">Fichiers du jeu</string>
<string name="games_prefix">Préfixe Proton</string>
<string name="games_saves_looking">Recherche des sauvegardes…</string>
@@ -1040,7 +1038,6 @@
<string name="games_saves_folders">%1$d dossiers de sauvegarde</string>
<string name="games_saves_summary">%1$s · %2$d fichiers · %3$s</string>
<string name="games_kb">%1$d Ko</string>
<string name="games_manage_saves">Gérer les sauvegardes</string>
<string name="games_saves">Sauvegardes de jeu</string>
<string name="games_import_saves">Importer des sauvegardes…</string>
<string name="games_export_gamehub">Exporter en tant qu\'archive GameHub…</string>
@@ -1052,7 +1049,6 @@
<string name="games_runtime_first_play">Installation de l\'environnement d\'exécution lors du premier lancement du jeu</string>
<string name="games_runtime_update">Mise à jour de l\'environnement d\'exécution disponible</string>
<string name="games_runtime_ready">● Environnement d\'exécution prêt</string>
<string name="games_last_played">Dernier lancement %1$s</string>
<string name="games_internal">Stockage interne</string>
<string name="games_added">Jeux ajoutés</string>
<string name="games_custom">Jeu personnalisé</string>
@@ -1121,7 +1117,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">La session Linux n\'est pas prête, le terminal ne peut pas être utilisé.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Jeux</string>
<string name="rail_games">Bibliothèque</string>
<string name="rail_desktop">Bureau</string>
<string name="rail_store">Boutique</string>
<string name="rail_components">Composants</string>
@@ -1970,7 +1966,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personnalisé</string>
<string name="setup_stores">Boutiques</string>
<string name="setup_stores_open_on">Ouvrir une boutique sur</string>
<string name="setup_stores_speed">Niveau de vitesse de téléchargement</string>
<string name="setup_stores_engine">Moteur des boutiques</string>
<string name="setup_stores_engine_chip_ready">● Moteur prêt</string>
@@ -1984,11 +1979,9 @@
<string name="stores_tabs_arrange">Organiser les onglets</string>
<string name="stores_tabs_arrange_done">Terminé</string>
<string name="stores_chip_signed_out">(non connecté)</string>
<string name="stores_tab_store">Boutique</string>
<string name="stores_tab_library">Bibliothèque</string>
<string name="stores_tab_all">Tous</string>
<string name="stores_tab_downloads">Téléchargements</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Se connecter</string>
<string name="stores_signout_named">%1$s · se déconnecter</string>
<string name="stores_signout_title">Se déconnecter de %1$s ?</string>
@@ -2006,7 +1999,6 @@
<string name="stores_shelf_deals">Promotions</string>
<string name="stores_shelf_free">Jeux gratuits</string>
<string name="stores_shelf_trending">Tendances</string>
<string name="stores_shelf_library">Votre bibliothèque</string>
<string name="stores_play">Jouer</string>
<string name="stores_install">Installer</string>
<string name="stores_resume_install">Reprendre l\'installation</string>
@@ -3044,4 +3036,38 @@
<string name="spatial_saved_views_hint">Les vues enregistrées peuvent être recalculées. Démarrez un nouvel étalonnage pour capturer dans le système de coordonnées de suivi actuel.</string>
<string name="spatial_model_checksum_error">Incohérence de la somme de contrôle du modèle manuel</string>
<string name="spatial_opencv_load_error">OpenCV n\'a pas pu charger</string>
<string name="epic_client_missing">Non installé</string>
<string name="epic_client_open">Ouvrir</string>
<string name="epic_client_install">Installer</string>
<string name="game_tab_about">À propos</string>
<string name="game_tab_install">Installer</string>
<string name="game_install_location">Emplacement</string>
<string name="game_install_folder">Dossier</string>
<string name="game_install_size">Taille sur le disque</string>
<string name="game_install_date">Installé</string>
<string name="stores_xbox_update">Mettre à jour / vérifier</string>
<string name="stores_xbox_manage">Gérer le contenu</string>
<string name="stores_xbox_close_first">Fermez ce jeu Xbox avant de le mettre à jour.</string>
<string name="stores_xbox_content_title">Contenu Xbox</string>
<string name="stores_xbox_content_none">Aucun pack de contenu PC sous licence n\'est disponible pour ce jeu.</string>
<string name="stores_xbox_content_update">%1$s · Mettre à jour / vérifier</string>
<string name="stores_xbox_content_install">%1$s · Installer</string>
<string name="stores_xbox_choose_content">Choisir le contenu PC</string>
<string name="stores_eyebrow_pass">disponible avec %1$s</string>
<string name="stores_owned_in_library">Jeux possédés dans la Bibliothèque</string>
<string name="library_filter_added">Ajoutés</string>
<string name="game_tab_launch">Lancement</string>
<string name="game_tab_saves_cloud">Sauvegardes et cloud</string>
<string name="game_launch_shortcut">Raccourci Steam</string>
<string name="game_install_version">Version</string>
<string name="game_install_played">Dernière partie</string>
<string name="game_install_access">Accès</string>
<string name="game_install_access_owned">Possédé</string>
<string name="game_stream">Streamer</string>
<string name="library_add">Ajouter à la Bibliothèque</string>
<string name="library_remove">Retirer de la Bibliothèque</string>
<string name="library_in">Dans la Bibliothèque</string>
<string name="xbox_filter_show">Afficher</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Paramètres cloud</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Steam में इंस्टॉल किए गेम सेशन के बाद यहां दिखते हैं।</string>
<string name="games_play_steam">Steam चलाएं</string>
<string name="games_play_steamvr">SteamVR चलाएं</string>
<string name="games_launch_settings">लॉन्च सेटिंग</string>
<string name="games_launch">लॉन्च करें</string>
<string name="games_files">गेम फ़ाइलें</string>
<string name="games_prefix">Proton प्रीफ़िक्स</string>
<string name="games_saves_looking">सेव खोजे जा रहे हैं…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d सेव फ़ोल्डर</string>
<string name="games_saves_summary">%1$s · %2$d फ़ाइलें · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">सेव मैनेज करें</string>
<string name="games_saves">गेम सेव</string>
<string name="games_import_saves">सेव इंपोर्ट करें…</string>
<string name="games_export_gamehub">GameHub zip के रूप में एक्सपोर्ट करें…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">पहली बार Play पर रनटाइम इंस्टॉल होता है</string>
<string name="games_runtime_update">रनटाइम अपडेट उपलब्ध</string>
<string name="games_runtime_ready">● रनटाइम तैयार</string>
<string name="games_last_played">आख़िरी बार %1$s को खेला</string>
<string name="games_internal">इंटरनल स्टोरेज</string>
<string name="games_added">जोड़ा गया गेम</string>
<string name="games_custom">कस्टम गेम</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux टर्मिनल</string>
<string name="second_terminal_not_ready">Linux सेशन अभी टर्मिनल के लिए तैयार नहीं है।</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">गेम</string>
<string name="rail_games">लाइब्रेरी</string>
<string name="rail_desktop">डेस्कटॉप</string>
<string name="rail_store">स्टोर</string>
<string name="rail_components">कॉम्पोनेंट</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">कस्टम</string>
<string name="setup_stores">स्टोर</string>
<string name="setup_stores_open_on">स्टोर खोलें</string>
<string name="setup_stores_speed">डाउनलोड स्पीड टियर</string>
<string name="setup_stores_engine">स्टोर इंजन</string>
<string name="setup_stores_engine_chip_ready">● इंजन तैयार</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">टैब व्यवस्थित करें</string>
<string name="stores_tabs_arrange_done">हो गया</string>
<string name="stores_chip_signed_out">(साइन इन नहीं है)</string>
<string name="stores_tab_store">स्टोर</string>
<string name="stores_tab_library">लाइब्रेरी</string>
<string name="stores_tab_all">सभी</string>
<string name="stores_tab_downloads">डाउनलोड</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">साइन इन करें</string>
<string name="stores_signout_named">%1$s · साइन आउट करें</string>
<string name="stores_signout_title">%1$s से साइन आउट करें?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">डील</string>
<string name="stores_shelf_free">फ़्री गेम</string>
<string name="stores_shelf_trending">ट्रेंडिंग</string>
<string name="stores_shelf_library">आपकी लाइब्रेरी</string>
<string name="stores_play">Play</string>
<string name="stores_install">इंस्टॉल करें</string>
<string name="stores_resume_install">इंस्टॉल फिर से शुरू करें</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">सहेजे गए दृश्यों की पुनर्गणना की जा सकती है। वर्तमान ट्रैकिंग समन्वय प्रणाली में कैप्चर करने के लिए एक नया अंशांकन प्रारंभ करें।</string>
<string name="spatial_model_checksum_error">हैंड मॉडल चेकसम बेमेल</string>
<string name="spatial_opencv_load_error">OpenCV लोड नहीं हो सका</string>
<string name="epic_client_missing">इंस्टॉल नहीं है</string>
<string name="epic_client_open">खोलें</string>
<string name="epic_client_install">इंस्टॉल करें</string>
<string name="game_tab_about">परिचय</string>
<string name="game_tab_install">इंस्टॉल करें</string>
<string name="game_install_location">जगह</string>
<string name="game_install_folder">फ़ोल्डर</string>
<string name="game_install_size">डिस्क पर आकार</string>
<string name="game_install_date">इंस्टॉल है</string>
<string name="stores_xbox_update">अपडेट / सत्यापित करें</string>
<string name="stores_xbox_manage">कंटेंट प्रबंधित करें</string>
<string name="stores_xbox_close_first">अपडेट करने से पहले यह Xbox गेम बंद करें।</string>
<string name="stores_xbox_content_title">Xbox कंटेंट</string>
<string name="stores_xbox_content_none">इस गेम के लिए कोई लाइसेंस वाला PC कंटेंट पैकेज उपलब्ध नहीं है।</string>
<string name="stores_xbox_content_update">%1$s · अपडेट / सत्यापित करें</string>
<string name="stores_xbox_content_install">%1$s · इंस्टॉल करें</string>
<string name="stores_xbox_choose_content">PC कंटेंट चुनें</string>
<string name="stores_eyebrow_pass">%1$s के साथ उपलब्ध</string>
<string name="stores_owned_in_library">लाइब्रेरी में आपके गेम</string>
<string name="library_filter_added">जोड़े गए</string>
<string name="game_tab_launch">लॉन्च</string>
<string name="game_tab_saves_cloud">सेव और क्लाउड</string>
<string name="game_launch_shortcut">Steam शॉर्टकट</string>
<string name="game_install_version">संस्करण</string>
<string name="game_install_played">आखिरी बार खेला</string>
<string name="game_install_access">एक्सेस</string>
<string name="game_install_access_owned">आपका</string>
<string name="game_stream">स्ट्रीम करें</string>
<string name="library_add">लाइब्रेरी में जोड़ें</string>
<string name="library_remove">लाइब्रेरी से हटाएँ</string>
<string name="library_in">लाइब्रेरी में</string>
<string name="xbox_filter_show">दिखाएँ</string>
<string name="xbox_filter_cloud">क्लाउड</string>
<string name="stores_xbox_cloud_settings">क्लाउड सेटिंग्स</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">A Steamben telepített játékok a munkamenet után itt jelennek meg.</string>
<string name="games_play_steam">Steam elindítása</string>
<string name="games_play_steamvr">SteamVR elindítása</string>
<string name="games_launch_settings">Indítási beállítások</string>
<string name="games_launch">Indítás</string>
<string name="games_files">Játékfájlok</string>
<string name="games_prefix">Proton-prefix</string>
<string name="games_saves_looking">Mentések keresése…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d mentésmappa</string>
<string name="games_saves_summary">%1$s · %2$d fájl · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Mentések kezelése</string>
<string name="games_saves">Játékmentések</string>
<string name="games_import_saves">Mentések importálása…</string>
<string name="games_export_gamehub">Exportálás GameHub zipként…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">A futtatókörnyezet az első Játék gombnyomáskor települ</string>
<string name="games_runtime_update">Elérhető futtatókörnyezet-frissítés</string>
<string name="games_runtime_ready">● A futtatókörnyezet kész</string>
<string name="games_last_played">Utoljára játszva: %1$s</string>
<string name="games_internal">Belső tárhely</string>
<string name="games_added">Hozzáadott játék</string>
<string name="games_custom">Egyéni játék</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux terminál</string>
<string name="second_terminal_not_ready">A Linux-munkamenet még nem áll készen a terminálra.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Játékok</string>
<string name="rail_games">Könyvtár</string>
<string name="rail_desktop">Asztal</string>
<string name="rail_store">Áruház</string>
<string name="rail_components">Összetevők</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Egyéni</string>
<string name="setup_stores">Áruházak</string>
<string name="setup_stores_open_on">Áruház megnyitása itt</string>
<string name="setup_stores_speed">Letöltési sebességi szint</string>
<string name="setup_stores_engine">Áruházmotor</string>
<string name="setup_stores_engine_chip_ready">● A motor kész</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Fülek rendezése</string>
<string name="stores_tabs_arrange_done">Kész</string>
<string name="stores_chip_signed_out">(nincs bejelentkezve)</string>
<string name="stores_tab_store">Áruház</string>
<string name="stores_tab_library">Könyvtár</string>
<string name="stores_tab_all">Összes</string>
<string name="stores_tab_downloads">Letöltések</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Bejelentkezés</string>
<string name="stores_signout_named">%1$s · kijelentkezés</string>
<string name="stores_signout_title">Kijelentkezik a %1$s-ből?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Akciók</string>
<string name="stores_shelf_free">Ingyenes játékok</string>
<string name="stores_shelf_trending">Felkapott</string>
<string name="stores_shelf_library">A könyvtára</string>
<string name="stores_play">Játék</string>
<string name="stores_install">Telepítés</string>
<string name="stores_resume_install">Telepítés folytatása</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_opencv_load_error">Az OpenCV betöltése nem sikerült</string>
<string name="glasses_3dof">3DoF virtuális képernyő</string>
<string name="glasses_6dof">6DoF virtuális képernyő</string>
<string name="epic_client_missing">Nincs telepítve</string>
<string name="epic_client_open">Megnyitás</string>
<string name="epic_client_install">Telepítés</string>
<string name="game_tab_about">Névjegy</string>
<string name="game_tab_install">Telepítés</string>
<string name="game_install_location">Hely</string>
<string name="game_install_folder">Mappa</string>
<string name="game_install_size">Méret a tárhelyen</string>
<string name="game_install_date">Telepítve</string>
<string name="stores_xbox_update">Frissítés / ellenőrzés</string>
<string name="stores_xbox_manage">Tartalom kezelése</string>
<string name="stores_xbox_close_first">Frissítés előtt zárd be ezt az Xbox-játékot.</string>
<string name="stores_xbox_content_title">Xbox-tartalom</string>
<string name="stores_xbox_content_none">Ehhez a játékhoz nincs licencelt PC-s tartalomcsomag.</string>
<string name="stores_xbox_content_update">%1$s · Frissítés / ellenőrzés</string>
<string name="stores_xbox_content_install">%1$s · Telepítés</string>
<string name="stores_xbox_choose_content">PC-s tartalom választása</string>
<string name="stores_eyebrow_pass">elérhető ezzel: %1$s</string>
<string name="stores_owned_in_library">Saját játékok a könyvtárban</string>
<string name="library_filter_added">Hozzáadott</string>
<string name="game_tab_launch">Indítás</string>
<string name="game_tab_saves_cloud">Mentések és felhő</string>
<string name="game_launch_shortcut">Steam-parancsikon</string>
<string name="game_install_version">Verzió</string>
<string name="game_install_played">Utoljára játszva</string>
<string name="game_install_access">Hozzáférés</string>
<string name="game_install_access_owned">Saját</string>
<string name="game_stream">Streamelés</string>
<string name="library_add">Hozzáadás a könyvtárhoz</string>
<string name="library_remove">Eltávolítás a könyvtárból</string>
<string name="library_in">A könyvtárban</string>
<string name="xbox_filter_show">Megjelenítés</string>
<string name="xbox_filter_cloud">Felhő</string>
<string name="stores_xbox_cloud_settings">Felhőbeállítások</string>
</resources>
+35 -9
View File
@@ -1017,8 +1017,6 @@
<string name="games_empty">Gim yang Anda instal di Steam akan muncul di sini setelah sesi.</string>
<string name="games_play_steam">Mainkan Steam</string>
<string name="games_play_steamvr">Mainkan SteamVR</string>
<string name="games_launch_settings">Setelan peluncuran</string>
<string name="games_launch">Luncurkan</string>
<string name="games_files">File gim</string>
<string name="games_prefix">Prefix Proton</string>
<string name="games_saves_looking">Mencari data simpanan…</string>
@@ -1026,7 +1024,6 @@
<string name="games_saves_folders">%1$d folder simpanan</string>
<string name="games_saves_summary">%1$s · %2$d file · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Kelola data simpanan</string>
<string name="games_saves">Data simpanan gim</string>
<string name="games_import_saves">Impor data simpanan…</string>
<string name="games_export_gamehub">Ekspor sebagai zip GameHub…</string>
@@ -1038,7 +1035,6 @@
<string name="games_runtime_first_play">Runtime terinstal saat Main pertama kali</string>
<string name="games_runtime_update">Pembaruan runtime tersedia</string>
<string name="games_runtime_ready">● Runtime siap</string>
<string name="games_last_played">Terakhir dimainkan %1$s</string>
<string name="games_internal">Penyimpanan internal</string>
<string name="games_added">Gim ditambahkan</string>
<string name="games_custom">Gim khusus</string>
@@ -1107,7 +1103,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">Sesi Linux belum siap untuk terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Game</string>
<string name="rail_games">Pustaka</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Toko</string>
<string name="rail_components">Komponen</string>
@@ -1926,7 +1922,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Khusus</string>
<string name="setup_stores">Toko</string>
<string name="setup_stores_open_on">Buka toko di</string>
<string name="setup_stores_speed">Tingkat kecepatan unduhan</string>
<string name="setup_stores_engine">Mesin toko</string>
<string name="setup_stores_engine_chip_ready">● Mesin siap</string>
@@ -1940,11 +1935,9 @@
<string name="stores_tabs_arrange">Atur tab</string>
<string name="stores_tabs_arrange_done">Selesai</string>
<string name="stores_chip_signed_out">(belum masuk)</string>
<string name="stores_tab_store">Toko</string>
<string name="stores_tab_library">Pustaka</string>
<string name="stores_tab_all">Semua</string>
<string name="stores_tab_downloads">Unduhan</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Masuk</string>
<string name="stores_signout_named">%1$s · keluar</string>
<string name="stores_signout_title">Keluar dari %1$s?</string>
@@ -1962,7 +1955,6 @@
<string name="stores_shelf_deals">Penawaran</string>
<string name="stores_shelf_free">Gim gratis</string>
<string name="stores_shelf_trending">Sedang tren</string>
<string name="stores_shelf_library">Pustaka Anda</string>
<string name="stores_play">Mainkan</string>
<string name="stores_install">Instal</string>
<string name="stores_resume_install">Lanjutkan instalasi</string>
@@ -2983,4 +2975,38 @@
<string name="spatial_saved_views_hint">Tampilan yang disimpan dapat dihitung ulang. Mulai kalibrasi baru untuk menangkap sistem koordinat pelacakan saat ini.</string>
<string name="spatial_model_checksum_error">Ketidakcocokan checksum model tangan</string>
<string name="spatial_opencv_load_error">OpenCV tidak dapat memuat</string>
<string name="epic_client_missing">Belum terinstal</string>
<string name="epic_client_open">Buka</string>
<string name="epic_client_install">Pasang</string>
<string name="game_tab_about">Tentang</string>
<string name="game_tab_install">Pasang</string>
<string name="game_install_location">Lokasi</string>
<string name="game_install_folder">Folder</string>
<string name="game_install_size">Ukuran di disk</string>
<string name="game_install_date">Terinstal</string>
<string name="stores_xbox_update">Perbarui / verifikasi</string>
<string name="stores_xbox_manage">Kelola konten</string>
<string name="stores_xbox_close_first">Tutup game Xbox ini sebelum memperbaruinya.</string>
<string name="stores_xbox_content_title">Konten Xbox</string>
<string name="stores_xbox_content_none">Tidak ada paket konten PC berlisensi untuk game ini.</string>
<string name="stores_xbox_content_update">%1$s · Perbarui / verifikasi</string>
<string name="stores_xbox_content_install">%1$s · Instal</string>
<string name="stores_xbox_choose_content">Pilih konten PC</string>
<string name="stores_eyebrow_pass">tersedia dengan %1$s</string>
<string name="stores_owned_in_library">Game yang dimiliki di Pustaka</string>
<string name="library_filter_added">Ditambahkan</string>
<string name="game_tab_launch">Peluncuran</string>
<string name="game_tab_saves_cloud">Simpanan &amp; cloud</string>
<string name="game_launch_shortcut">Pintasan Steam</string>
<string name="game_install_version">Versi</string>
<string name="game_install_played">Terakhir dimainkan</string>
<string name="game_install_access">Akses</string>
<string name="game_install_access_owned">Dimiliki</string>
<string name="game_stream">Streaming</string>
<string name="library_add">Tambahkan ke Pustaka</string>
<string name="library_remove">Hapus dari Pustaka</string>
<string name="library_in">Di Pustaka</string>
<string name="xbox_filter_show">Tampilkan</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Pengaturan cloud</string>
</resources>
+35 -9
View File
@@ -1021,8 +1021,6 @@
<string name="games_empty">I giochi che installi in Steam compaiono qui dopo la sessione.</string>
<string name="games_play_steam">Gioca su Steam</string>
<string name="games_play_steamvr">Gioca su SteamVR</string>
<string name="games_launch_settings">Impostazioni di avvio</string>
<string name="games_launch">Avvia</string>
<string name="games_files">File di gioco</string>
<string name="games_prefix">Prefisso Proton</string>
<string name="games_saves_looking">Ricerca dei salvataggi…</string>
@@ -1030,7 +1028,6 @@
<string name="games_saves_folders">%1$d cartelle di salvataggio</string>
<string name="games_saves_summary">%1$s · %2$d file · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Gestisci salvataggi</string>
<string name="games_saves">Salvataggi di gioco</string>
<string name="games_import_saves">Importa salvataggi…</string>
<string name="games_export_gamehub">Esporta come zip GameHub…</string>
@@ -1042,7 +1039,6 @@
<string name="games_runtime_first_play">Il runtime si installa al primo Gioca</string>
<string name="games_runtime_update">Aggiornamento del runtime disponibile</string>
<string name="games_runtime_ready">● Runtime pronto</string>
<string name="games_last_played">Giocato l\'ultima volta %1$s</string>
<string name="games_internal">Memoria interna</string>
<string name="games_added">Gioco aggiunto</string>
<string name="games_custom">Gioco personalizzato</string>
@@ -1111,7 +1107,7 @@
<string name="second_terminal">Terminale Linux</string>
<string name="second_terminal_not_ready">La sessione Linux non è ancora pronta per un terminale.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Giochi</string>
<string name="rail_games">Libreria</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Negozio</string>
<string name="rail_components">Componenti</string>
@@ -1960,7 +1956,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizzati</string>
<string name="setup_stores">Negozi</string>
<string name="setup_stores_open_on">Apri un negozio su</string>
<string name="setup_stores_speed">Livello di velocità di download</string>
<string name="setup_stores_engine">Motore dei negozi</string>
<string name="setup_stores_engine_chip_ready">● Motore pronto</string>
@@ -1974,11 +1969,9 @@
<string name="stores_tabs_arrange">Disponi schede</string>
<string name="stores_tabs_arrange_done">Fine</string>
<string name="stores_chip_signed_out">(accesso non eseguito)</string>
<string name="stores_tab_store">Negozio</string>
<string name="stores_tab_library">Libreria</string>
<string name="stores_tab_all">Tutti</string>
<string name="stores_tab_downloads">Download</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Accedi</string>
<string name="stores_signout_named">%1$s · esci</string>
<string name="stores_signout_title">Uscire da %1$s?</string>
@@ -1996,7 +1989,6 @@
<string name="stores_shelf_deals">Offerte</string>
<string name="stores_shelf_free">Giochi gratuiti</string>
<string name="stores_shelf_trending">Di tendenza</string>
<string name="stores_shelf_library">La tua libreria</string>
<string name="stores_play">Gioca</string>
<string name="stores_install">Installa</string>
<string name="stores_resume_install">Riprendi installazione</string>
@@ -3042,4 +3034,38 @@
<string name="spatial_opencv_load_error">Impossibile caricare OpenCV</string>
<string name="setup_glasses_scope">Le impostazioni dello schermo e del tracciamento della testa sono condivise tra menu, giochi e sessioni desktop di DroidDeck.</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">Non installata</string>
<string name="epic_client_open">Apri</string>
<string name="epic_client_install">Installa</string>
<string name="game_tab_about">Informazioni</string>
<string name="game_tab_install">Installa</string>
<string name="game_install_location">Posizione</string>
<string name="game_install_folder">Cartella</string>
<string name="game_install_size">Spazio su disco</string>
<string name="game_install_date">Installati</string>
<string name="stores_xbox_update">Aggiorna / verifica</string>
<string name="stores_xbox_manage">Gestisci contenuti</string>
<string name="stores_xbox_close_first">Chiudi questo gioco Xbox prima di aggiornarlo.</string>
<string name="stores_xbox_content_title">Contenuti Xbox</string>
<string name="stores_xbox_content_none">Non ci sono pacchetti di contenuti PC con licenza per questo gioco.</string>
<string name="stores_xbox_content_update">%1$s · Aggiorna / verifica</string>
<string name="stores_xbox_content_install">%1$s · Installa</string>
<string name="stores_xbox_choose_content">Scegli i contenuti PC</string>
<string name="stores_eyebrow_pass">disponibile con %1$s</string>
<string name="stores_owned_in_library">Giochi posseduti nella Libreria</string>
<string name="library_filter_added">Aggiunti</string>
<string name="game_tab_launch">Avvio</string>
<string name="game_tab_saves_cloud">Salvataggi e cloud</string>
<string name="game_launch_shortcut">Collegamento Steam</string>
<string name="game_install_version">Versione</string>
<string name="game_install_played">Ultima partita</string>
<string name="game_install_access">Accesso</string>
<string name="game_install_access_owned">Posseduto</string>
<string name="game_stream">Riproduci in streaming</string>
<string name="library_add">Aggiungi alla Libreria</string>
<string name="library_remove">Rimuovi dalla Libreria</string>
<string name="library_in">Nella Libreria</string>
<string name="xbox_filter_show">Mostra</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Impostazioni cloud</string>
</resources>
+35 -9
View File
@@ -1019,8 +1019,6 @@
<string name="games_empty">Steamでインストールされたゲームは、セッション終了後にここに表示されます。</string>
<string name="games_play_steam">Steamを実行</string>
<string name="games_play_steamvr">SteamVRを実行</string>
<string name="games_launch_settings">起動設定</string>
<string name="games_launch">起動</string>
<string name="games_files">ゲームファイル</string>
<string name="games_prefix">Protonプレフィックス</string>
<string name="games_saves_looking">セーブデータを検索中…</string>
@@ -1028,7 +1026,6 @@
<string name="games_saves_folders">%1$d 個のセーブフォルダ</string>
<string name="games_saves_summary">%1$s · %2$d 個のファイル · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">セーブデータの管理</string>
<string name="games_saves">ゲームセーブデータ</string>
<string name="games_import_saves">セーブデータをインポート…</string>
<string name="games_export_gamehub">GameHubアーカイブとしてエクスポート…</string>
@@ -1040,7 +1037,6 @@
<string name="games_runtime_first_play">初回実行時に実行環境を導入</string>
<string name="games_runtime_update">実行環境にアップデートがあります</string>
<string name="games_runtime_ready">● 実行環境の準備完了</string>
<string name="games_last_played">最終プレイ %1$s</string>
<string name="games_internal">内部ストレージ</string>
<string name="games_added">追加されたゲーム</string>
<string name="games_custom">カスタムゲーム</string>
@@ -1109,7 +1105,7 @@
<string name="second_terminal">Linuxターミナル</string>
<string name="second_terminal_not_ready">Linuxセッションの準備ができていないため、ターミナルを使用できません。</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">ゲーム</string>
<string name="rail_games">ライブラリ</string>
<string name="rail_desktop">デスクトップ</string>
<string name="rail_store">ストア</string>
<string name="rail_components">コンポーネント</string>
@@ -1928,7 +1924,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">カスタム</string>
<string name="setup_stores">ストア</string>
<string name="setup_stores_open_on">ストアページを開くときの初期タブ</string>
<string name="setup_stores_speed">ダウンロード速度レベル</string>
<string name="setup_stores_engine">ストアエンジン</string>
<string name="setup_stores_engine_chip_ready">● エンジン準備完了</string>
@@ -1942,11 +1937,9 @@
<string name="stores_tabs_arrange">タブを整理</string>
<string name="stores_tabs_arrange_done">完了</string>
<string name="stores_chip_signed_out">(未ログイン)</string>
<string name="stores_tab_store">ストア</string>
<string name="stores_tab_library">ライブラリ</string>
<string name="stores_tab_all">すべて</string>
<string name="stores_tab_downloads">ダウンロード</string>
<string name="stores_tab_count">%1$s(%2$d)</string>
<string name="stores_signin">ログイン</string>
<string name="stores_signout_named">%1$s · ログアウト</string>
<string name="stores_signout_title">%1$sからログアウトしますか?</string>
@@ -1964,7 +1957,6 @@
<string name="stores_shelf_deals">セール</string>
<string name="stores_shelf_free">無料ゲーム</string>
<string name="stores_shelf_trending">トレンド</string>
<string name="stores_shelf_library">マイライブラリ</string>
<string name="stores_play">プレイ</string>
<string name="stores_install">インストール</string>
<string name="stores_resume_install">インストールを再開</string>
@@ -2985,4 +2977,38 @@
<string name="spatial_opencv_load_error">OpenCVをロードできませんでした</string>
<string name="tracking_recovery_look">明るい部屋でゆっくりと周囲を見回してください。\nトラッキングカメラを遮るものがないように、家具などの見えるものに顔を向けてください。</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">未インストール</string>
<string name="epic_client_open">開く</string>
<string name="epic_client_install">インストール</string>
<string name="game_tab_about">概要</string>
<string name="game_tab_install">インストール</string>
<string name="game_install_location">場所</string>
<string name="game_install_folder">フォルダ</string>
<string name="game_install_size">ディスク上のサイズ</string>
<string name="game_install_date">インストール済み</string>
<string name="stores_xbox_update">更新 / 検証</string>
<string name="stores_xbox_manage">コンテンツを管理</string>
<string name="stores_xbox_close_first">更新する前にこの Xbox ゲームを閉じてください。</string>
<string name="stores_xbox_content_title">Xbox コンテンツ</string>
<string name="stores_xbox_content_none">このゲームにはライセンス済みの PC コンテンツパッケージがありません。</string>
<string name="stores_xbox_content_update">%1$s · 更新 / 検証</string>
<string name="stores_xbox_content_install">%1$s · インストール</string>
<string name="stores_xbox_choose_content">PC コンテンツを選択</string>
<string name="stores_eyebrow_pass">%1$s で利用可能</string>
<string name="stores_owned_in_library">ライブラリの所有ゲーム</string>
<string name="library_filter_added">追加したゲーム</string>
<string name="game_tab_launch">起動</string>
<string name="game_tab_saves_cloud">セーブとクラウド</string>
<string name="game_launch_shortcut">Steam ショートカット</string>
<string name="game_install_version">バージョン</string>
<string name="game_install_played">最終プレイ</string>
<string name="game_install_access">アクセス</string>
<string name="game_install_access_owned">所有</string>
<string name="game_stream">ストリーミング</string>
<string name="library_add">ライブラリに追加</string>
<string name="library_remove">ライブラリから削除</string>
<string name="library_in">ライブラリにあります</string>
<string name="xbox_filter_show">表示</string>
<string name="xbox_filter_cloud">クラウド</string>
<string name="stores_xbox_cloud_settings">クラウド設定</string>
</resources>
+35 -9
View File
@@ -1019,8 +1019,6 @@
<string name="games_empty">Steam에서 설치한 게임은 세션 종료 후 여기에 표시됩니다.</string>
<string name="games_play_steam">Steam 실행</string>
<string name="games_play_steamvr">SteamVR 실행</string>
<string name="games_launch_settings">실행 설정</string>
<string name="games_launch">실행</string>
<string name="games_files">게임 파일</string>
<string name="games_prefix">Proton 프리픽스</string>
<string name="games_saves_looking">저장 데이터 검색 중…</string>
@@ -1028,7 +1026,6 @@
<string name="games_saves_folders">%1$d개 저장 폴더</string>
<string name="games_saves_summary">%1$s · %2$d개 파일 · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">저장 데이터 관리</string>
<string name="games_saves">게임 저장 데이터</string>
<string name="games_import_saves">저장 데이터 가져오기…</string>
<string name="games_export_gamehub">GameHub 아카이브로 내보내기…</string>
@@ -1040,7 +1037,6 @@
<string name="games_runtime_first_play">처음 \"Steam 실행\" 시 런타임 설치</string>
<string name="games_runtime_update">런타임 업데이트 있음</string>
<string name="games_runtime_ready">● 런타임 준비됨</string>
<string name="games_last_played">마지막 실행 %1$s</string>
<string name="games_internal">내부 저장 공간</string>
<string name="games_added">추가된 게임</string>
<string name="games_custom">사용자 정의 게임</string>
@@ -1109,7 +1105,7 @@
<string name="second_terminal">Linux 터미널</string>
<string name="second_terminal_not_ready">Linux 세션이 준비되지 않아 터미널을 사용할 수 없습니다.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">게임</string>
<string name="rail_games">라이브러리</string>
<string name="rail_desktop">데스크톱</string>
<string name="rail_store">스토어</string>
<string name="rail_components">구성 요소</string>
@@ -1928,7 +1924,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">사용자 정의</string>
<string name="setup_stores">스토어</string>
<string name="setup_stores_open_on">스토어를 열 때 표시</string>
<string name="setup_stores_speed">다운로드 속도 단계</string>
<string name="setup_stores_engine">스토어 엔진</string>
<string name="setup_stores_engine_chip_ready">● 엔진 준비됨</string>
@@ -1942,11 +1937,9 @@
<string name="stores_tabs_arrange">탭 정리</string>
<string name="stores_tabs_arrange_done">완료</string>
<string name="stores_chip_signed_out">(로그인 안 됨)</string>
<string name="stores_tab_store">스토어</string>
<string name="stores_tab_library">라이브러리</string>
<string name="stores_tab_all">전체</string>
<string name="stores_tab_downloads">다운로드</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">로그인</string>
<string name="stores_signout_named">%1$s · 로그아웃</string>
<string name="stores_signout_title">%1$s에서 로그아웃하시겠습니까?</string>
@@ -1964,7 +1957,6 @@
<string name="stores_shelf_deals">할인</string>
<string name="stores_shelf_free">무료 게임</string>
<string name="stores_shelf_trending">인기 급상승</string>
<string name="stores_shelf_library">내 라이브러리</string>
<string name="stores_play">플레이</string>
<string name="stores_install">설치</string>
<string name="stores_resume_install">설치 재개</string>
@@ -2985,4 +2977,38 @@
<string name="spatial_distance_result">카메라 측정 %1$s; 선택한 거리 %2$s. %3$s</string>
<string name="spatial_wait_previews">보정을 시작하기 전에 두 카메라 미리보기를 모두 기다리세요.</string>
<string name="spatial_saved_views_hint">저장된 뷰는 다시 계산될 수 있습니다. 현재 추적 좌표계에서 캡처하려면 새 보정을 시작하세요.</string>
<string name="epic_client_missing">설치되지 않음</string>
<string name="epic_client_open">열기</string>
<string name="epic_client_install">설치</string>
<string name="game_tab_about">정보</string>
<string name="game_tab_install">설치</string>
<string name="game_install_location">위치</string>
<string name="game_install_folder">폴더</string>
<string name="game_install_size">디스크 사용량</string>
<string name="game_install_date">설치됨</string>
<string name="stores_xbox_update">업데이트 / 확인</string>
<string name="stores_xbox_manage">콘텐츠 관리</string>
<string name="stores_xbox_close_first">업데이트하기 전에 이 Xbox 게임을 닫으세요.</string>
<string name="stores_xbox_content_title">Xbox 콘텐츠</string>
<string name="stores_xbox_content_none">이 게임에 사용할 수 있는 라이선스된 PC 콘텐츠 패키지가 없습니다.</string>
<string name="stores_xbox_content_update">%1$s · 업데이트 / 확인</string>
<string name="stores_xbox_content_install">%1$s · 설치</string>
<string name="stores_xbox_choose_content">PC 콘텐츠 선택</string>
<string name="stores_eyebrow_pass">%1$s 이용 가능</string>
<string name="stores_owned_in_library">라이브러리의 보유 게임</string>
<string name="library_filter_added">추가됨</string>
<string name="game_tab_launch">실행</string>
<string name="game_tab_saves_cloud">저장 및 클라우드</string>
<string name="game_launch_shortcut">Steam 바로가기</string>
<string name="game_install_version">버전</string>
<string name="game_install_played">마지막 플레이</string>
<string name="game_install_access">이용 권한</string>
<string name="game_install_access_owned">보유</string>
<string name="game_stream">스트리밍</string>
<string name="library_add">라이브러리에 추가</string>
<string name="library_remove">라이브러리에서 제거</string>
<string name="library_in">라이브러리에 있음</string>
<string name="xbox_filter_show">표시</string>
<string name="xbox_filter_cloud">클라우드</string>
<string name="stores_xbox_cloud_settings">클라우드 설정</string>
</resources>
+35 -9
View File
@@ -1017,8 +1017,6 @@
<string name="games_empty">Permainan yang anda pasang dalam Steam muncul di sini selepas sesi.</string>
<string name="games_play_steam">Main Steam</string>
<string name="games_play_steamvr">Main SteamVR</string>
<string name="games_launch_settings">Tetapan pelancaran</string>
<string name="games_launch">Lancarkan</string>
<string name="games_files">Fail permainan</string>
<string name="games_prefix">Prefix Proton</string>
<string name="games_saves_looking">Mencari simpanan…</string>
@@ -1026,7 +1024,6 @@
<string name="games_saves_folders">%1$d folder simpanan</string>
<string name="games_saves_summary">%1$s · %2$d fail · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Urus simpanan</string>
<string name="games_saves">Simpanan permainan</string>
<string name="games_import_saves">Import simpanan…</string>
<string name="games_export_gamehub">Eksport sebagai zip GameHub…</string>
@@ -1038,7 +1035,6 @@
<string name="games_runtime_first_play">Masa jalan dipasang pada Main pertama</string>
<string name="games_runtime_update">Kemas kini masa jalan tersedia</string>
<string name="games_runtime_ready">● Masa jalan sedia</string>
<string name="games_last_played">Terakhir dimainkan %1$s</string>
<string name="games_internal">Storan dalaman</string>
<string name="games_added">Permainan ditambah</string>
<string name="games_custom">Permainan tersuai</string>
@@ -1107,7 +1103,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">Sesi Linux belum sedia untuk terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Permainan</string>
<string name="rail_games">Pustaka</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Kedai</string>
<string name="rail_components">Komponen</string>
@@ -1926,7 +1922,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Tersuai</string>
<string name="setup_stores">Kedai</string>
<string name="setup_stores_open_on">Buka kedai pada</string>
<string name="setup_stores_speed">Tahap kelajuan muat turun</string>
<string name="setup_stores_engine">Enjin kedai</string>
<string name="setup_stores_engine_chip_ready">● Enjin sedia</string>
@@ -1940,11 +1935,9 @@
<string name="stores_tabs_arrange">Susun tab</string>
<string name="stores_tabs_arrange_done">Selesai</string>
<string name="stores_chip_signed_out">(belum log masuk)</string>
<string name="stores_tab_store">Kedai</string>
<string name="stores_tab_library">Pustaka</string>
<string name="stores_tab_all">Semua</string>
<string name="stores_tab_downloads">Muat turun</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Log masuk</string>
<string name="stores_signout_named">%1$s · log keluar</string>
<string name="stores_signout_title">Log keluar daripada %1$s?</string>
@@ -1962,7 +1955,6 @@
<string name="stores_shelf_deals">Tawaran</string>
<string name="stores_shelf_free">Permainan percuma</string>
<string name="stores_shelf_trending">Tular</string>
<string name="stores_shelf_library">Pustaka anda</string>
<string name="stores_play">Main</string>
<string name="stores_install">Pasang</string>
<string name="stores_resume_install">Sambung pemasangan</string>
@@ -2983,4 +2975,38 @@
<string name="spatial_opencv_load_error">OpenCV tidak dapat dimuatkan</string>
<string name="spatial_print_title">DroidDeck - Penentukuran Luma Ultra</string>
<string name="spatial_base_loaded">Penentukuran asas Luma Ultra dimuatkan</string>
<string name="epic_client_missing">Belum dipasang</string>
<string name="epic_client_open">Buka</string>
<string name="epic_client_install">Pasang</string>
<string name="game_tab_about">Perihal</string>
<string name="game_tab_install">Pasang</string>
<string name="game_install_location">Lokasi</string>
<string name="game_install_folder">Folder</string>
<string name="game_install_size">Saiz pada cakera</string>
<string name="game_install_date">Dipasang</string>
<string name="stores_xbox_update">Kemas kini / sahkan</string>
<string name="stores_xbox_manage">Urus kandungan</string>
<string name="stores_xbox_close_first">Tutup permainan Xbox ini sebelum mengemas kininya.</string>
<string name="stores_xbox_content_title">Kandungan Xbox</string>
<string name="stores_xbox_content_none">Tiada pakej kandungan PC berlesen untuk permainan ini.</string>
<string name="stores_xbox_content_update">%1$s · Kemas kini / sahkan</string>
<string name="stores_xbox_content_install">%1$s · Pasang</string>
<string name="stores_xbox_choose_content">Pilih kandungan PC</string>
<string name="stores_eyebrow_pass">tersedia dengan %1$s</string>
<string name="stores_owned_in_library">Permainan dimiliki dalam Pustaka</string>
<string name="library_filter_added">Ditambah</string>
<string name="game_tab_launch">Pelancaran</string>
<string name="game_tab_saves_cloud">Simpanan &amp; awan</string>
<string name="game_launch_shortcut">Pintasan Steam</string>
<string name="game_install_version">Versi</string>
<string name="game_install_played">Terakhir dimainkan</string>
<string name="game_install_access">Akses</string>
<string name="game_install_access_owned">Dimiliki</string>
<string name="game_stream">Strim</string>
<string name="library_add">Tambah ke Pustaka</string>
<string name="library_remove">Alih keluar dari Pustaka</string>
<string name="library_in">Dalam Pustaka</string>
<string name="xbox_filter_show">Tunjukkan</string>
<string name="xbox_filter_cloud">Awan</string>
<string name="stores_xbox_cloud_settings">Tetapan awan</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Spill du installerer i Steam, vises her etter sesjonen.</string>
<string name="games_play_steam">Spill Steam</string>
<string name="games_play_steamvr">Spill SteamVR</string>
<string name="games_launch_settings">Startinnstillinger</string>
<string name="games_launch">Start</string>
<string name="games_files">Spillfiler</string>
<string name="games_prefix">Proton-prefiks</string>
<string name="games_saves_looking">Ser etter lagringer…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d lagringsmapper</string>
<string name="games_saves_summary">%1$s · %2$d filer · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Behandle lagringer</string>
<string name="games_saves">Spilllagringer</string>
<string name="games_import_saves">Importer lagringer…</string>
<string name="games_export_gamehub">Eksporter som GameHub-zip…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Kjøretiden installeres ved første Spill</string>
<string name="games_runtime_update">Kjøretidsoppdatering tilgjengelig</string>
<string name="games_runtime_ready">● Kjøretid klar</string>
<string name="games_last_played">Sist spilt %1$s</string>
<string name="games_internal">Intern lagring</string>
<string name="games_added">Lagt til spill</string>
<string name="games_custom">Egendefinert spill</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux-terminal</string>
<string name="second_terminal_not_ready">Linux-sesjonen er ikke klar for en terminal ennå.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Spill</string>
<string name="rail_games">Bibliotek</string>
<string name="rail_desktop">Skrivebord</string>
<string name="rail_store">Butikk</string>
<string name="rail_components">Komponenter</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Egendefinert</string>
<string name="setup_stores">Butikker</string>
<string name="setup_stores_open_on">Åpne en butikk på</string>
<string name="setup_stores_speed">Nedlastingshastighetsnivå</string>
<string name="setup_stores_engine">Butikkmotor</string>
<string name="setup_stores_engine_chip_ready">● Motor klar</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Ordne faner</string>
<string name="stores_tabs_arrange_done">Ferdig</string>
<string name="stores_chip_signed_out">(ikke innlogget)</string>
<string name="stores_tab_store">Butikk</string>
<string name="stores_tab_library">Bibliotek</string>
<string name="stores_tab_all">Alle</string>
<string name="stores_tab_downloads">Nedlastinger</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Logg inn</string>
<string name="stores_signout_named">%1$s · logg ut</string>
<string name="stores_signout_title">Logge ut av %1$s?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Tilbud</string>
<string name="stores_shelf_free">Gratisspill</string>
<string name="stores_shelf_trending">Trender</string>
<string name="stores_shelf_library">Biblioteket ditt</string>
<string name="stores_play">Spill</string>
<string name="stores_install">Installer</string>
<string name="stores_resume_install">Gjenoppta installasjon</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_saved_views_hint">Lagrede visninger kan beregnes på nytt. Start en ny kalibrering for å fange opp i gjeldende sporingskoordinatsystem.</string>
<string name="spatial_model_checksum_error">Uoverensstemmelse med håndmodellsjekksum</string>
<string name="spatial_opencv_load_error">OpenCV kunne ikke laste</string>
<string name="epic_client_missing">Ikke installert</string>
<string name="epic_client_open">Åpne</string>
<string name="epic_client_install">Installer</string>
<string name="game_tab_about">Om</string>
<string name="game_tab_install">Installer</string>
<string name="game_install_location">Plassering</string>
<string name="game_install_folder">Mappe</string>
<string name="game_install_size">Størrelse på disk</string>
<string name="game_install_date">Installert</string>
<string name="stores_xbox_update">Oppdater / bekreft</string>
<string name="stores_xbox_manage">Administrer innhold</string>
<string name="stores_xbox_close_first">Lukk dette Xbox-spillet før du oppdaterer det.</string>
<string name="stores_xbox_content_title">Xbox-innhold</string>
<string name="stores_xbox_content_none">Det finnes ingen lisensierte PC-innholdspakker for dette spillet.</string>
<string name="stores_xbox_content_update">%1$s · Oppdater / bekreft</string>
<string name="stores_xbox_content_install">%1$s · Installer</string>
<string name="stores_xbox_choose_content">Velg PC-innhold</string>
<string name="stores_eyebrow_pass">tilgjengelig med %1$s</string>
<string name="stores_owned_in_library">Eide spill i Bibliotek</string>
<string name="library_filter_added">Lagt til</string>
<string name="game_tab_launch">Start</string>
<string name="game_tab_saves_cloud">Lagrede spill og sky</string>
<string name="game_launch_shortcut">Steam-snarvei</string>
<string name="game_install_version">Versjon</string>
<string name="game_install_played">Sist spilt</string>
<string name="game_install_access">Tilgang</string>
<string name="game_install_access_owned">Eid</string>
<string name="game_stream">Strøm</string>
<string name="library_add">Legg til i Bibliotek</string>
<string name="library_remove">Fjern fra Bibliotek</string>
<string name="library_in">I Bibliotek</string>
<string name="xbox_filter_show">Vis</string>
<string name="xbox_filter_cloud">Sky</string>
<string name="stores_xbox_cloud_settings">Skyinnstillinger</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Games die je in Steam installeert, verschijnen hier na de sessie.</string>
<string name="games_play_steam">Steam spelen</string>
<string name="games_play_steamvr">SteamVR spelen</string>
<string name="games_launch_settings">Opstartinstellingen</string>
<string name="games_launch">Starten</string>
<string name="games_files">Gamebestanden</string>
<string name="games_prefix">Proton-prefix</string>
<string name="games_saves_looking">Opslagbestanden zoeken…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d opslagmappen</string>
<string name="games_saves_summary">%1$s · %2$d bestanden · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Opslagbestanden beheren</string>
<string name="games_saves">Gameopslag</string>
<string name="games_import_saves">Opslagbestanden importeren…</string>
<string name="games_export_gamehub">Exporteren als GameHub-zip…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Runtime wordt geïnstalleerd bij de eerste keer Spelen</string>
<string name="games_runtime_update">Runtime-update beschikbaar</string>
<string name="games_runtime_ready">● Runtime gereed</string>
<string name="games_last_played">Laatst gespeeld %1$s</string>
<string name="games_internal">Interne opslag</string>
<string name="games_added">Toegevoegde game</string>
<string name="games_custom">Aangepaste game</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux-terminal</string>
<string name="second_terminal_not_ready">De Linux-sessie is nog niet klaar voor een terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Games</string>
<string name="rail_games">Bibliotheek</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Winkel</string>
<string name="rail_components">Componenten</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Aangepast</string>
<string name="setup_stores">Winkels</string>
<string name="setup_stores_open_on">Open een winkel op</string>
<string name="setup_stores_speed">Niveau voor downloadsnelheid</string>
<string name="setup_stores_engine">Winkels-engine</string>
<string name="setup_stores_engine_chip_ready">● Engine gereed</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Tabbladen rangschikken</string>
<string name="stores_tabs_arrange_done">Klaar</string>
<string name="stores_chip_signed_out">(niet aangemeld)</string>
<string name="stores_tab_store">Winkel</string>
<string name="stores_tab_library">Bibliotheek</string>
<string name="stores_tab_all">Alle</string>
<string name="stores_tab_downloads">Downloads</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Aanmelden</string>
<string name="stores_signout_named">%1$s · afmelden</string>
<string name="stores_signout_title">Afmelden bij %1$s?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Aanbiedingen</string>
<string name="stores_shelf_free">Gratis games</string>
<string name="stores_shelf_trending">Populair</string>
<string name="stores_shelf_library">Jouw bibliotheek</string>
<string name="stores_play">Spelen</string>
<string name="stores_install">Installeren</string>
<string name="stores_resume_install">Installatie hervatten</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_opencv_load_error">OpenCV kon niet laden</string>
<string name="glasses_6dof">Virtueel scherm met 6DoF</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">Niet geïnstalleerd</string>
<string name="epic_client_open">Openen</string>
<string name="epic_client_install">Installeren</string>
<string name="game_tab_about">Over</string>
<string name="game_tab_install">Installeren</string>
<string name="game_install_location">Locatie</string>
<string name="game_install_folder">Map</string>
<string name="game_install_size">Grootte op schijf</string>
<string name="game_install_date">Geïnstalleerd</string>
<string name="stores_xbox_update">Bijwerken / controleren</string>
<string name="stores_xbox_manage">Inhoud beheren</string>
<string name="stores_xbox_close_first">Sluit deze Xbox-game voordat je hem bijwerkt.</string>
<string name="stores_xbox_content_title">Xbox-inhoud</string>
<string name="stores_xbox_content_none">Er zijn geen gelicentieerde pc-inhoudspakketten voor deze game.</string>
<string name="stores_xbox_content_update">%1$s · Bijwerken / controleren</string>
<string name="stores_xbox_content_install">%1$s · Installeren</string>
<string name="stores_xbox_choose_content">Pc-inhoud kiezen</string>
<string name="stores_eyebrow_pass">beschikbaar met %1$s</string>
<string name="stores_owned_in_library">Games in je Bibliotheek</string>
<string name="library_filter_added">Toegevoegd</string>
<string name="game_tab_launch">Starten</string>
<string name="game_tab_saves_cloud">Opslag en cloud</string>
<string name="game_launch_shortcut">Steam-snelkoppeling</string>
<string name="game_install_version">Versie</string>
<string name="game_install_played">Laatst gespeeld</string>
<string name="game_install_access">Toegang</string>
<string name="game_install_access_owned">In bezit</string>
<string name="game_stream">Streamen</string>
<string name="library_add">Toevoegen aan Bibliotheek</string>
<string name="library_remove">Verwijderen uit Bibliotheek</string>
<string name="library_in">In Bibliotheek</string>
<string name="xbox_filter_show">Weergeven</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Cloudinstellingen</string>
</resources>
+35 -9
View File
@@ -1035,8 +1035,6 @@
<string name="games_empty">Gry, które instalujesz w Steam, pojawią się tutaj po sesji.</string>
<string name="games_play_steam">Zagraj w Steam</string>
<string name="games_play_steamvr">Zagraj w SteamVR</string>
<string name="games_launch_settings">Ustawienia uruchamiania</string>
<string name="games_launch">Uruchom</string>
<string name="games_files">Pliki gry</string>
<string name="games_prefix">Prefiks Proton</string>
<string name="games_saves_looking">Wyszukiwanie zapisów…</string>
@@ -1044,7 +1042,6 @@
<string name="games_saves_folders">%1$d folderów zapisów</string>
<string name="games_saves_summary">%1$s · %2$d plików · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Zarządzaj zapisami</string>
<string name="games_saves">Zapisy gry</string>
<string name="games_import_saves">Importuj zapisy…</string>
<string name="games_export_gamehub">Eksportuj jako zip GameHub…</string>
@@ -1056,7 +1053,6 @@
<string name="games_runtime_first_play">Środowisko instaluje się przy pierwszym uruchomieniu</string>
<string name="games_runtime_update">Dostępna aktualizacja środowiska</string>
<string name="games_runtime_ready">● Środowisko gotowe</string>
<string name="games_last_played">Ostatnio grano %1$s</string>
<string name="games_internal">Pamięć wewnętrzna</string>
<string name="games_added">Dodana gra</string>
<string name="games_custom">Własna gra</string>
@@ -1125,7 +1121,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">Sesja Linux nie jest jeszcze gotowa na terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Gry</string>
<string name="rail_games">Biblioteka</string>
<string name="rail_desktop">Pulpit</string>
<string name="rail_store">Sklep</string>
<string name="rail_components">Komponenty</string>
@@ -1989,7 +1985,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Własna</string>
<string name="setup_stores">Sklepy</string>
<string name="setup_stores_open_on">Otwórz sklep w</string>
<string name="setup_stores_speed">Poziom szybkości pobierania</string>
<string name="setup_stores_engine">Mechanizm sklepów</string>
<string name="setup_stores_engine_chip_ready">● Mechanizm gotowy</string>
@@ -2003,11 +1998,9 @@
<string name="stores_tabs_arrange">Ułóż karty</string>
<string name="stores_tabs_arrange_done">Gotowe</string>
<string name="stores_chip_signed_out">(niezalogowano)</string>
<string name="stores_tab_store">Sklep</string>
<string name="stores_tab_library">Biblioteka</string>
<string name="stores_tab_all">Wszystkie</string>
<string name="stores_tab_downloads">Pobierania</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Zaloguj się</string>
<string name="stores_signout_named">%1$s · wyloguj się</string>
<string name="stores_signout_title">Wylogować się z %1$s?</string>
@@ -2025,7 +2018,6 @@
<string name="stores_shelf_deals">Promocje</string>
<string name="stores_shelf_free">Darmowe gry</string>
<string name="stores_shelf_trending">Popularne</string>
<string name="stores_shelf_library">Twoja biblioteka</string>
<string name="stores_play">Graj</string>
<string name="stores_install">Zainstaluj</string>
<string name="stores_resume_install">Wznów instalację</string>
@@ -3079,4 +3071,38 @@
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="spatial_connect_hint">Podłącz Luma Ultra i włącz śledzenie pozycji.</string>
<string name="spatial_inference_error">Wykrywanie dłoni jest niedostępne: %1$s. Zmień ustawienie CPU/GPU lub wyłącz i ponownie włącz sterowanie dłońmi, aby spróbować ponownie.</string>
<string name="epic_client_missing">Nie zainstalowano</string>
<string name="epic_client_open">Otwórz</string>
<string name="epic_client_install">Zainstaluj</string>
<string name="game_tab_about">Informacje</string>
<string name="game_tab_install">Zainstaluj</string>
<string name="game_install_location">Lokalizacja</string>
<string name="game_install_folder">Folder</string>
<string name="game_install_size">Rozmiar na dysku</string>
<string name="game_install_date">Zainstalowane</string>
<string name="stores_xbox_update">Aktualizuj / zweryfikuj</string>
<string name="stores_xbox_manage">Zarządzaj zawartością</string>
<string name="stores_xbox_close_first">Zamknij tę grę Xbox przed aktualizacją.</string>
<string name="stores_xbox_content_title">Zawartość Xbox</string>
<string name="stores_xbox_content_none">Dla tej gry nie ma licencjonowanych pakietów zawartości na PC.</string>
<string name="stores_xbox_content_update">%1$s · Aktualizuj / zweryfikuj</string>
<string name="stores_xbox_content_install">%1$s · Zainstaluj</string>
<string name="stores_xbox_choose_content">Wybierz zawartość na PC</string>
<string name="stores_eyebrow_pass">dostępna w %1$s</string>
<string name="stores_owned_in_library">Posiadane gry w bibliotece</string>
<string name="library_filter_added">Dodane</string>
<string name="game_tab_launch">Uruchamianie</string>
<string name="game_tab_saves_cloud">Zapisy i chmura</string>
<string name="game_launch_shortcut">Skrót Steam</string>
<string name="game_install_version">Wersja</string>
<string name="game_install_played">Ostatnio grano</string>
<string name="game_install_access">Dostęp</string>
<string name="game_install_access_owned">Posiadana</string>
<string name="game_stream">Streamuj</string>
<string name="library_add">Dodaj do biblioteki</string>
<string name="library_remove">Usuń z biblioteki</string>
<string name="library_in">W bibliotece</string>
<string name="xbox_filter_show">Pokaż</string>
<string name="xbox_filter_cloud">Chmura</string>
<string name="stores_xbox_cloud_settings">Ustawienia chmury</string>
</resources>
+35 -9
View File
@@ -1021,8 +1021,6 @@
<string name="games_empty">Os jogos que instalar no Steam aparecem aqui após a sessão.</string>
<string name="games_play_steam">Jogar no Steam</string>
<string name="games_play_steamvr">Jogar no SteamVR</string>
<string name="games_launch_settings">Definições de arranque</string>
<string name="games_launch">Iniciar</string>
<string name="games_files">Ficheiros do jogo</string>
<string name="games_prefix">Prefixo do Proton</string>
<string name="games_saves_looking">A procurar gravações…</string>
@@ -1030,7 +1028,6 @@
<string name="games_saves_folders">%1$d pastas de gravações</string>
<string name="games_saves_summary">%1$s · %2$d ficheiros · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Gerir gravações</string>
<string name="games_saves">Gravações do jogo</string>
<string name="games_import_saves">Importar gravações…</string>
<string name="games_export_gamehub">Exportar como zip do GameHub…</string>
@@ -1042,7 +1039,6 @@
<string name="games_runtime_first_play">O ambiente de execução é instalado ao tocar em Jogar pela primeira vez</string>
<string name="games_runtime_update">Atualização do ambiente de execução disponível</string>
<string name="games_runtime_ready">● Ambiente de execução pronto</string>
<string name="games_last_played">Jogado pela última vez %1$s</string>
<string name="games_internal">Armazenamento interno</string>
<string name="games_added">Jogo adicionado</string>
<string name="games_custom">Jogo personalizado</string>
@@ -1111,7 +1107,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">A sessão Linux ainda não está pronta para um terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Jogos</string>
<string name="rail_games">Biblioteca</string>
<string name="rail_desktop">Ambiente de trabalho</string>
<string name="rail_store">Loja</string>
<string name="rail_components">Componentes</string>
@@ -1960,7 +1956,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizados</string>
<string name="setup_stores">Lojas</string>
<string name="setup_stores_open_on">Abrir uma loja em</string>
<string name="setup_stores_speed">Nível de velocidade de transferência</string>
<string name="setup_stores_engine">Motor das lojas</string>
<string name="setup_stores_engine_chip_ready">● Motor pronto</string>
@@ -1974,11 +1969,9 @@
<string name="stores_tabs_arrange">Organizar separadores</string>
<string name="stores_tabs_arrange_done">Concluído</string>
<string name="stores_chip_signed_out">(sessão não iniciada)</string>
<string name="stores_tab_store">Loja</string>
<string name="stores_tab_library">Biblioteca</string>
<string name="stores_tab_all">Todos</string>
<string name="stores_tab_downloads">Transferências</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Iniciar sessão</string>
<string name="stores_signout_named">%1$s · terminar sessão</string>
<string name="stores_signout_title">Terminar sessão em %1$s?</string>
@@ -1996,7 +1989,6 @@
<string name="stores_shelf_deals">Promoções</string>
<string name="stores_shelf_free">Jogos gratuitos</string>
<string name="stores_shelf_trending">Tendências</string>
<string name="stores_shelf_library">A sua biblioteca</string>
<string name="stores_play">Jogar</string>
<string name="stores_install">Instalar</string>
<string name="stores_resume_install">Retomar instalação</string>
@@ -3042,4 +3034,38 @@
<string name="spatial_model_checksum_error">Incompatibilidade de checksum do modelo de mãos</string>
<string name="spatial_opencv_load_error">O OpenCV não conseguiu carregar</string>
<string name="tracking_recovery_look">Olhe em redor lentamente num ambiente bem iluminado.\nMantenha as câmaras de seguimento desobstruídas e viradas para móveis ou outros detalhes visíveis.</string>
<string name="epic_client_missing">Não instalado</string>
<string name="epic_client_open">Abrir</string>
<string name="epic_client_install">Instalar</string>
<string name="game_tab_about">Acerca de</string>
<string name="game_tab_install">Instalar</string>
<string name="game_install_location">Localização</string>
<string name="game_install_folder">Pasta</string>
<string name="game_install_size">Tamanho no disco</string>
<string name="game_install_date">Instalado</string>
<string name="stores_xbox_update">Atualizar / verificar</string>
<string name="stores_xbox_manage">Gerir conteúdo</string>
<string name="stores_xbox_close_first">Feche este jogo Xbox antes de o atualizar.</string>
<string name="stores_xbox_content_title">Conteúdo Xbox</string>
<string name="stores_xbox_content_none">Não há pacotes de conteúdo para PC licenciados para este jogo.</string>
<string name="stores_xbox_content_update">%1$s · Atualizar / verificar</string>
<string name="stores_xbox_content_install">%1$s · Instalar</string>
<string name="stores_xbox_choose_content">Escolher conteúdo para PC</string>
<string name="stores_eyebrow_pass">disponível com %1$s</string>
<string name="stores_owned_in_library">Jogos adquiridos na Biblioteca</string>
<string name="library_filter_added">Adicionados</string>
<string name="game_tab_launch">Arranque</string>
<string name="game_tab_saves_cloud">Gravações e nuvem</string>
<string name="game_launch_shortcut">Atalho do Steam</string>
<string name="game_install_version">Versão</string>
<string name="game_install_played">Jogado pela última vez</string>
<string name="game_install_access">Acesso</string>
<string name="game_install_access_owned">Adquirido</string>
<string name="game_stream">Transmitir</string>
<string name="library_add">Adicionar à Biblioteca</string>
<string name="library_remove">Remover da Biblioteca</string>
<string name="library_in">Na Biblioteca</string>
<string name="xbox_filter_show">Mostrar</string>
<string name="xbox_filter_cloud">Nuvem</string>
<string name="stores_xbox_cloud_settings">Definições da nuvem</string>
</resources>
+35 -9
View File
@@ -1021,8 +1021,6 @@
<string name="games_empty">Os jogos que você instala na Steam aparecem aqui após a sessão.</string>
<string name="games_play_steam">Jogar na Steam</string>
<string name="games_play_steamvr">Jogar no SteamVR</string>
<string name="games_launch_settings">Configurações de inicialização</string>
<string name="games_launch">Iniciar</string>
<string name="games_files">Arquivos do jogo</string>
<string name="games_prefix">Prefixo do Proton</string>
<string name="games_saves_looking">Procurando jogos salvos…</string>
@@ -1030,7 +1028,6 @@
<string name="games_saves_folders">%1$d pastas de jogos salvos</string>
<string name="games_saves_summary">%1$s · %2$d arquivos · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Gerenciar jogos salvos</string>
<string name="games_saves">Jogos salvos</string>
<string name="games_import_saves">Importar jogos salvos…</string>
<string name="games_export_gamehub">Exportar como zip do GameHub…</string>
@@ -1042,7 +1039,6 @@
<string name="games_runtime_first_play">O runtime é instalado no primeiro Jogar</string>
<string name="games_runtime_update">Atualização do runtime disponível</string>
<string name="games_runtime_ready">● Runtime pronto</string>
<string name="games_last_played">Jogado por último: %1$s</string>
<string name="games_internal">Armazenamento interno</string>
<string name="games_added">Jogo adicionado</string>
<string name="games_custom">Jogo personalizado</string>
@@ -1111,7 +1107,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">A sessão Linux ainda não está pronta para um terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Jogos</string>
<string name="rail_games">Biblioteca</string>
<string name="rail_desktop">Área de trabalho</string>
<string name="rail_store">Loja</string>
<string name="rail_components">Componentes</string>
@@ -1960,7 +1956,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizado</string>
<string name="setup_stores">Lojas</string>
<string name="setup_stores_open_on">Abrir uma loja em</string>
<string name="setup_stores_speed">Nível de velocidade de download</string>
<string name="setup_stores_engine">Mecanismo das lojas</string>
<string name="setup_stores_engine_chip_ready">● Mecanismo pronto</string>
@@ -1974,11 +1969,9 @@
<string name="stores_tabs_arrange">Organizar abas</string>
<string name="stores_tabs_arrange_done">Concluído</string>
<string name="stores_chip_signed_out">(sem login)</string>
<string name="stores_tab_store">Loja</string>
<string name="stores_tab_library">Biblioteca</string>
<string name="stores_tab_all">Todos</string>
<string name="stores_tab_downloads">Downloads</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Fazer login</string>
<string name="stores_signout_named">%1$s · sair</string>
<string name="stores_signout_title">Sair de %1$s?</string>
@@ -1996,7 +1989,6 @@
<string name="stores_shelf_deals">Ofertas</string>
<string name="stores_shelf_free">Jogos grátis</string>
<string name="stores_shelf_trending">Em alta</string>
<string name="stores_shelf_library">Sua biblioteca</string>
<string name="stores_play">Jogar</string>
<string name="stores_install">Instalar</string>
<string name="stores_resume_install">Retomar instalação</string>
@@ -3042,4 +3034,38 @@
<string name="spatial_model_checksum_error">Incompatibilidade de soma de verificação do modelo de mãos</string>
<string name="spatial_opencv_load_error">OpenCV não pôde carregar</string>
<string name="tracking_recovery_look">Olhe ao redor lentamente em um ambiente bem iluminado.\nMantenha as câmeras de rastreamento desobstruídas e voltadas para móveis ou outros detalhes visíveis.</string>
<string name="epic_client_missing">Não instalado</string>
<string name="epic_client_open">Abrir</string>
<string name="epic_client_install">Instalar</string>
<string name="game_tab_about">Sobre</string>
<string name="game_tab_install">Instalar</string>
<string name="game_install_location">Local</string>
<string name="game_install_folder">Pasta</string>
<string name="game_install_size">Tamanho em disco</string>
<string name="game_install_date">Instalados</string>
<string name="stores_xbox_update">Atualizar / verificar</string>
<string name="stores_xbox_manage">Gerenciar conteúdo</string>
<string name="stores_xbox_close_first">Feche este jogo do Xbox antes de atualizá-lo.</string>
<string name="stores_xbox_content_title">Conteúdo do Xbox</string>
<string name="stores_xbox_content_none">Não há pacotes de conteúdo para PC licenciados para este jogo.</string>
<string name="stores_xbox_content_update">%1$s · Atualizar / verificar</string>
<string name="stores_xbox_content_install">%1$s · Instalar</string>
<string name="stores_xbox_choose_content">Escolher conteúdo para PC</string>
<string name="stores_eyebrow_pass">disponível com %1$s</string>
<string name="stores_owned_in_library">Jogos comprados na Biblioteca</string>
<string name="library_filter_added">Adicionados</string>
<string name="game_tab_launch">Inicialização</string>
<string name="game_tab_saves_cloud">Salvamentos e nuvem</string>
<string name="game_launch_shortcut">Atalho do Steam</string>
<string name="game_install_version">Versão</string>
<string name="game_install_played">Jogado pela última vez</string>
<string name="game_install_access">Acesso</string>
<string name="game_install_access_owned">Comprado</string>
<string name="game_stream">Transmitir</string>
<string name="library_add">Adicionar à Biblioteca</string>
<string name="library_remove">Remover da Biblioteca</string>
<string name="library_in">Na Biblioteca</string>
<string name="xbox_filter_show">Mostrar</string>
<string name="xbox_filter_cloud">Nuvem</string>
<string name="stores_xbox_cloud_settings">Configurações da nuvem</string>
</resources>
+35 -9
View File
@@ -1029,8 +1029,6 @@
<string name="games_empty">Jocurile pe care le instalezi în Steam apar aici după sesiune.</string>
<string name="games_play_steam">Joacă Steam</string>
<string name="games_play_steamvr">Joacă SteamVR</string>
<string name="games_launch_settings">Setări de lansare</string>
<string name="games_launch">Lansează</string>
<string name="games_files">Fișierele jocului</string>
<string name="games_prefix">Prefix Proton</string>
<string name="games_saves_looking">Se caută salvările…</string>
@@ -1038,7 +1036,6 @@
<string name="games_saves_folders">%1$d foldere de salvare</string>
<string name="games_saves_summary">%1$s · %2$d fișiere · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Gestionează salvările</string>
<string name="games_saves">Salvările jocului</string>
<string name="games_import_saves">Importă salvări…</string>
<string name="games_export_gamehub">Exportă ca arhivă GameHub…</string>
@@ -1050,7 +1047,6 @@
<string name="games_runtime_first_play">Mediul de execuție se instalează la prima apăsare pe Play</string>
<string name="games_runtime_update">Actualizare disponibilă pentru mediul de execuție</string>
<string name="games_runtime_ready">● Mediu de execuție pregătit</string>
<string name="games_last_played">Jucat ultima dată %1$s</string>
<string name="games_internal">Stocare internă</string>
<string name="games_added">Joc adăugat</string>
<string name="games_custom">Joc personalizat</string>
@@ -1119,7 +1115,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">Sesiunea Linux nu este încă pregătită pentru un terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Jocuri</string>
<string name="rail_games">Bibliotecă</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Magazin</string>
<string name="rail_components">Componente</string>
@@ -1968,7 +1964,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Personalizat</string>
<string name="setup_stores">Magazine</string>
<string name="setup_stores_open_on">Deschide un magazin pe</string>
<string name="setup_stores_speed">Nivelul vitezei de descărcare</string>
<string name="setup_stores_engine">Motorul magazinelor</string>
<string name="setup_stores_engine_chip_ready">● Motor pregătit</string>
@@ -1982,11 +1977,9 @@
<string name="stores_tabs_arrange">Aranjează filele</string>
<string name="stores_tabs_arrange_done">Gata</string>
<string name="stores_chip_signed_out">(neautentificat)</string>
<string name="stores_tab_store">Magazin</string>
<string name="stores_tab_library">Bibliotecă</string>
<string name="stores_tab_all">Toate</string>
<string name="stores_tab_downloads">Descărcări</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Autentificare</string>
<string name="stores_signout_named">%1$s · deconectare</string>
<string name="stores_signout_title">Te deconectezi de la %1$s?</string>
@@ -2004,7 +1997,6 @@
<string name="stores_shelf_deals">Oferte</string>
<string name="stores_shelf_free">Jocuri gratuite</string>
<string name="stores_shelf_trending">În tendințe</string>
<string name="stores_shelf_library">Biblioteca ta</string>
<string name="stores_play">Joacă</string>
<string name="stores_install">Instalează</string>
<string name="stores_resume_install">Reia instalarea</string>
@@ -3042,4 +3034,38 @@
<string name="spatial_saved_views_hint">Vizualizările salvate pot fi recalculate. Începe o nouă calibrare pentru a captura în sistemul de coordonate de urmărire curent.</string>
<string name="spatial_model_checksum_error">Nepotrivire a sumei de control a modelului de mâini</string>
<string name="spatial_opencv_load_error">OpenCV nu s-a putut încărca</string>
<string name="epic_client_missing">Neinstalat</string>
<string name="epic_client_open">Deschide</string>
<string name="epic_client_install">Instalează</string>
<string name="game_tab_about">Despre</string>
<string name="game_tab_install">Instalează</string>
<string name="game_install_location">Locație</string>
<string name="game_install_folder">Folder</string>
<string name="game_install_size">Dimensiune pe disc</string>
<string name="game_install_date">Instalat</string>
<string name="stores_xbox_update">Actualizează / verifică</string>
<string name="stores_xbox_manage">Gestionează conținutul</string>
<string name="stores_xbox_close_first">Închide acest joc Xbox înainte de a-l actualiza.</string>
<string name="stores_xbox_content_title">Conținut Xbox</string>
<string name="stores_xbox_content_none">Nu există pachete de conținut PC licențiate pentru acest joc.</string>
<string name="stores_xbox_content_update">%1$s · Actualizează / verifică</string>
<string name="stores_xbox_content_install">%1$s · Instalează</string>
<string name="stores_xbox_choose_content">Alege conținutul PC</string>
<string name="stores_eyebrow_pass">disponibil cu %1$s</string>
<string name="stores_owned_in_library">Jocuri deținute în Bibliotecă</string>
<string name="library_filter_added">Adăugate</string>
<string name="game_tab_launch">Lansare</string>
<string name="game_tab_saves_cloud">Salvări și cloud</string>
<string name="game_launch_shortcut">Scurtătură Steam</string>
<string name="game_install_version">Versiune</string>
<string name="game_install_played">Jucat ultima dată</string>
<string name="game_install_access">Acces</string>
<string name="game_install_access_owned">Deținut</string>
<string name="game_stream">Redă în flux</string>
<string name="library_add">Adaugă în Bibliotecă</string>
<string name="library_remove">Elimină din Bibliotecă</string>
<string name="library_in">În Bibliotecă</string>
<string name="xbox_filter_show">Afișează</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Setări cloud</string>
</resources>
+35 -9
View File
@@ -1037,8 +1037,6 @@
<string name="games_empty">Игры, установленные в Steam, появятся здесь после завершения сессии.</string>
<string name="games_play_steam">Запустить Steam</string>
<string name="games_play_steamvr">Запустить SteamVR</string>
<string name="games_launch_settings">Настройки запуска</string>
<string name="games_launch">Запустить</string>
<string name="games_files">Файлы игры</string>
<string name="games_prefix">Префикс Proton</string>
<string name="games_saves_looking">Поиск сохранений…</string>
@@ -1046,7 +1044,6 @@
<string name="games_saves_folders">Папок сохранений: %1$d</string>
<string name="games_saves_summary">%1$s · файлов: %2$d · %3$s</string>
<string name="games_kb">%1$d КБ</string>
<string name="games_manage_saves">Управление сохранениями</string>
<string name="games_saves">Сохранения игры</string>
<string name="games_import_saves">Импорт сохранений…</string>
<string name="games_export_gamehub">Экспорт как архив GameHub…</string>
@@ -1058,7 +1055,6 @@
<string name="games_runtime_first_play">Среда выполнения установится при первом запуске игры</string>
<string name="games_runtime_update">Доступно обновление среды выполнения</string>
<string name="games_runtime_ready">● Среда выполнения готова</string>
<string name="games_last_played">Последний запуск %1$s</string>
<string name="games_internal">Внутреннее хранилище</string>
<string name="games_added">Добавленная игра</string>
<string name="games_custom">Своя игра</string>
@@ -1127,7 +1123,7 @@
<string name="second_terminal">Терминал Linux</string>
<string name="second_terminal_not_ready">Сессия Linux не готова, терминал недоступен.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Игры</string>
<string name="rail_games">Библиотека</string>
<string name="rail_desktop">Рабочий стол</string>
<string name="rail_store">Магазин</string>
<string name="rail_components">Компоненты</string>
@@ -1991,7 +1987,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Свои</string>
<string name="setup_stores">Магазины</string>
<string name="setup_stores_open_on">Открывать магазин на</string>
<string name="setup_stores_speed">Уровень скорости загрузки</string>
<string name="setup_stores_engine">Движок магазинов</string>
<string name="setup_stores_engine_chip_ready">● Движок готов</string>
@@ -2005,11 +2000,9 @@
<string name="stores_tabs_arrange">Упорядочить вкладки</string>
<string name="stores_tabs_arrange_done">Готово</string>
<string name="stores_chip_signed_out">(вход не выполнен)</string>
<string name="stores_tab_store">Магазин</string>
<string name="stores_tab_library">Библиотека</string>
<string name="stores_tab_all">Все</string>
<string name="stores_tab_downloads">Загрузки</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Войти</string>
<string name="stores_signout_named">%1$s · выйти</string>
<string name="stores_signout_title">Выйти из %1$s?</string>
@@ -2027,7 +2020,6 @@
<string name="stores_shelf_deals">Скидки</string>
<string name="stores_shelf_free">Бесплатные игры</string>
<string name="stores_shelf_trending">Популярное</string>
<string name="stores_shelf_library">Ваша библиотека</string>
<string name="stores_play">Играть</string>
<string name="stores_install">Установить</string>
<string name="stores_resume_install">Продолжить установку</string>
@@ -3078,4 +3070,38 @@
<string name="spatial_gpu">Распознавание рук на GPU</string>
<string name="spatial_gpu_hint">По умолчанию используется CPU. Режим GPU экспериментальный и может вызывать перезапуск SteamVR. Выбор сбрасывается при перезапуске отслеживания.</string>
<string name="spatial_inference_error">Распознавание рук недоступно: %1$s. Измените режим CPU/GPU или выключите и снова включите управление руками, чтобы повторить попытку.</string>
<string name="epic_client_missing">Не установлено</string>
<string name="epic_client_open">Открыть</string>
<string name="epic_client_install">Установить</string>
<string name="game_tab_about">О приложении</string>
<string name="game_tab_install">Установить</string>
<string name="game_install_location">Расположение</string>
<string name="game_install_folder">Папка</string>
<string name="game_install_size">Размер на диске</string>
<string name="game_install_date">Установлен</string>
<string name="stores_xbox_update">Обновить / проверить</string>
<string name="stores_xbox_manage">Управление контентом</string>
<string name="stores_xbox_close_first">Закройте эту игру Xbox перед обновлением.</string>
<string name="stores_xbox_content_title">Контент Xbox</string>
<string name="stores_xbox_content_none">Для этой игры нет лицензированных пакетов контента для ПК.</string>
<string name="stores_xbox_content_update">%1$s · Обновить / проверить</string>
<string name="stores_xbox_content_install">%1$s · Установить</string>
<string name="stores_xbox_choose_content">Выберите контент для ПК</string>
<string name="stores_eyebrow_pass">доступно с %1$s</string>
<string name="stores_owned_in_library">Ваши игры в библиотеке</string>
<string name="library_filter_added">Добавленные</string>
<string name="game_tab_launch">Запуск</string>
<string name="game_tab_saves_cloud">Сохранения и облако</string>
<string name="game_launch_shortcut">Ярлык Steam</string>
<string name="game_install_version">Версия</string>
<string name="game_install_played">Последний запуск</string>
<string name="game_install_access">Доступ</string>
<string name="game_install_access_owned">Куплена</string>
<string name="game_stream">Стриминг</string>
<string name="library_add">Добавить в библиотеку</string>
<string name="library_remove">Убрать из библиотеки</string>
<string name="library_in">В библиотеке</string>
<string name="xbox_filter_show">Показать</string>
<string name="xbox_filter_cloud">Облако</string>
<string name="stores_xbox_cloud_settings">Настройки облака</string>
</resources>
+35 -9
View File
@@ -1023,8 +1023,6 @@
<string name="games_empty">Spel du installerar i Steam visas här efter sessionen.</string>
<string name="games_play_steam">Spela Steam</string>
<string name="games_play_steamvr">Spela SteamVR</string>
<string name="games_launch_settings">Startinställningar</string>
<string name="games_launch">Starta</string>
<string name="games_files">Spelfiler</string>
<string name="games_prefix">Proton-prefix</string>
<string name="games_saves_looking">Letar efter sparfiler…</string>
@@ -1032,7 +1030,6 @@
<string name="games_saves_folders">%1$d sparmappar</string>
<string name="games_saves_summary">%1$s · %2$d filer · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Hantera sparfiler</string>
<string name="games_saves">Sparfiler</string>
<string name="games_import_saves">Importera sparfiler…</string>
<string name="games_export_gamehub">Exportera som GameHub-zip…</string>
@@ -1044,7 +1041,6 @@
<string name="games_runtime_first_play">Körmiljön installeras första gången du trycker på Play</string>
<string name="games_runtime_update">Uppdatering av körmiljön tillgänglig</string>
<string name="games_runtime_ready">● Körmiljö redo</string>
<string name="games_last_played">Senast spelad %1$s</string>
<string name="games_internal">Internt lagringsutrymme</string>
<string name="games_added">Tillagt spel</string>
<string name="games_custom">Anpassat spel</string>
@@ -1113,7 +1109,7 @@
<string name="second_terminal">Linux-terminal</string>
<string name="second_terminal_not_ready">Linux-sessionen är inte redo för en terminal än.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Spel</string>
<string name="rail_games">Bibliotek</string>
<string name="rail_desktop">Skrivbord</string>
<string name="rail_store">Butik</string>
<string name="rail_components">Komponenter</string>
@@ -1947,7 +1943,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Egna</string>
<string name="setup_stores">Butiker</string>
<string name="setup_stores_open_on">Öppna en butik på</string>
<string name="setup_stores_speed">Nedladdningshastighet</string>
<string name="setup_stores_engine">Butiksmotor</string>
<string name="setup_stores_engine_chip_ready">● Motor redo</string>
@@ -1961,11 +1956,9 @@
<string name="stores_tabs_arrange">Ordna flikar</string>
<string name="stores_tabs_arrange_done">Klart</string>
<string name="stores_chip_signed_out">(inte inloggad)</string>
<string name="stores_tab_store">Butik</string>
<string name="stores_tab_library">Bibliotek</string>
<string name="stores_tab_all">Alla</string>
<string name="stores_tab_downloads">Nedladdningar</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Logga in</string>
<string name="stores_signout_named">%1$s · logga ut</string>
<string name="stores_signout_title">Logga ut från %1$s?</string>
@@ -1983,7 +1976,6 @@
<string name="stores_shelf_deals">Erbjudanden</string>
<string name="stores_shelf_free">Gratisspel</string>
<string name="stores_shelf_trending">Populärt nu</string>
<string name="stores_shelf_library">Ditt bibliotek</string>
<string name="stores_play">Spela</string>
<string name="stores_install">Installera</string>
<string name="stores_resume_install">Återuppta installation</string>
@@ -3008,4 +3000,38 @@
<string name="spatial_model_checksum_error">Kontrollsumman för handmodellen inte överensstämmer</string>
<string name="spatial_opencv_load_error">OpenCV kunde inte laddas</string>
<string name="glasses_6dof">Virtuell skärm med 6DoF</string>
<string name="epic_client_missing">Inte installerad</string>
<string name="epic_client_open">Öppna</string>
<string name="epic_client_install">Installera</string>
<string name="game_tab_about">Om</string>
<string name="game_tab_install">Installera</string>
<string name="game_install_location">Plats</string>
<string name="game_install_folder">Mapp</string>
<string name="game_install_size">Storlek på disk</string>
<string name="game_install_date">Installerad</string>
<string name="stores_xbox_update">Uppdatera / verifiera</string>
<string name="stores_xbox_manage">Hantera innehåll</string>
<string name="stores_xbox_close_first">Stäng det här Xbox-spelet innan du uppdaterar det.</string>
<string name="stores_xbox_content_title">Xbox-innehåll</string>
<string name="stores_xbox_content_none">Det finns inga licensierade PC-innehållspaket för det här spelet.</string>
<string name="stores_xbox_content_update">%1$s · Uppdatera / verifiera</string>
<string name="stores_xbox_content_install">%1$s · Installera</string>
<string name="stores_xbox_choose_content">Välj PC-innehåll</string>
<string name="stores_eyebrow_pass">tillgängligt med %1$s</string>
<string name="stores_owned_in_library">Ägda spel i Bibliotek</string>
<string name="library_filter_added">Tillagda</string>
<string name="game_tab_launch">Start</string>
<string name="game_tab_saves_cloud">Sparade spel och moln</string>
<string name="game_launch_shortcut">Steam-genväg</string>
<string name="game_install_version">Version</string>
<string name="game_install_played">Senast spelat</string>
<string name="game_install_access">Åtkomst</string>
<string name="game_install_access_owned">Ägt</string>
<string name="game_stream">Strömma</string>
<string name="library_add">Lägg till i Bibliotek</string>
<string name="library_remove">Ta bort från Bibliotek</string>
<string name="library_in">I Bibliotek</string>
<string name="xbox_filter_show">Visa</string>
<string name="xbox_filter_cloud">Moln</string>
<string name="stores_xbox_cloud_settings">Molninställningar</string>
</resources>
+35 -9
View File
@@ -1017,8 +1017,6 @@
<string name="games_empty">เกมที่คุณติดตั้งใน Steam จะปรากฏที่นี่หลังจากจบเซสชัน</string>
<string name="games_play_steam">เล่น Steam</string>
<string name="games_play_steamvr">เล่น SteamVR</string>
<string name="games_launch_settings">การตั้งค่าการเปิดเกม</string>
<string name="games_launch">เปิดเกม</string>
<string name="games_files">ไฟล์เกม</string>
<string name="games_prefix">Prefix ของ Proton</string>
<string name="games_saves_looking">กำลังค้นหาเซฟ…</string>
@@ -1026,7 +1024,6 @@
<string name="games_saves_folders">%1$d โฟลเดอร์เซฟ</string>
<string name="games_saves_summary">%1$s · %2$d ไฟล์ · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">จัดการเซฟ</string>
<string name="games_saves">เซฟของเกม</string>
<string name="games_import_saves">นำเข้าเซฟ…</string>
<string name="games_export_gamehub">ส่งออกเป็น GameHub zip…</string>
@@ -1038,7 +1035,6 @@
<string name="games_runtime_first_play">รันไทม์จะติดตั้งเมื่อกด Play ครั้งแรก</string>
<string name="games_runtime_update">มีอัปเดตรันไทม์</string>
<string name="games_runtime_ready">● รันไทม์พร้อมใช้งาน</string>
<string name="games_last_played">เล่นล่าสุด %1$s</string>
<string name="games_internal">พื้นที่จัดเก็บภายใน</string>
<string name="games_added">เกมที่เพิ่ม</string>
<string name="games_custom">เกมกำหนดเอง</string>
@@ -1107,7 +1103,7 @@
<string name="second_terminal">เทอร์มินัล Linux</string>
<string name="second_terminal_not_ready">เซสชัน Linux ยังไม่พร้อมสำหรับเทอร์มินัล</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">เกม</string>
<string name="rail_games">ไลบรารี</string>
<string name="rail_desktop">เดสก์ท็อป</string>
<string name="rail_store">สโตร์</string>
<string name="rail_components">Components</string>
@@ -1926,7 +1922,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">กำหนดเอง</string>
<string name="setup_stores">สโตร์</string>
<string name="setup_stores_open_on">เปิดสโตร์บน</string>
<string name="setup_stores_speed">ระดับความเร็วการดาวน์โหลด</string>
<string name="setup_stores_engine">เอนจินสโตร์</string>
<string name="setup_stores_engine_chip_ready">● เอนจินพร้อมใช้งาน</string>
@@ -1940,11 +1935,9 @@
<string name="stores_tabs_arrange">จัดเรียงแท็บ</string>
<string name="stores_tabs_arrange_done">เสร็จสิ้น</string>
<string name="stores_chip_signed_out">(ยังไม่ลงชื่อเข้าใช้)</string>
<string name="stores_tab_store">สโตร์</string>
<string name="stores_tab_library">ไลบรารี</string>
<string name="stores_tab_all">ทั้งหมด</string>
<string name="stores_tab_downloads">การดาวน์โหลด</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">ลงชื่อเข้าใช้</string>
<string name="stores_signout_named">%1$s · ลงชื่อออก</string>
<string name="stores_signout_title">ลงชื่อออกจาก %1$s?</string>
@@ -1962,7 +1955,6 @@
<string name="stores_shelf_deals">ดีล</string>
<string name="stores_shelf_free">เกมฟรี</string>
<string name="stores_shelf_trending">กำลังเป็นที่นิยม</string>
<string name="stores_shelf_library">ไลบรารีของคุณ</string>
<string name="stores_play">เล่น</string>
<string name="stores_install">ติดตั้ง</string>
<string name="stores_resume_install">ดำเนินการติดตั้งต่อ</string>
@@ -2983,4 +2975,38 @@
<string name="spatial_model_checksum_error">ผลรวมตรวจสอบแบบจำลองมือไม่ตรงกัน</string>
<string name="spatial_opencv_load_error">OpenCV ไม่สามารถโหลดได้</string>
<string name="spatial_restored">กู้คืนมุมมองที่บันทึกไว้แล้ว %1$d รายการ คำนวณการสอบเทียบเพื่อลองอีกครั้ง หรือเริ่มชุดใหม่เพื่อบันทึกข้อมูลเพิ่มเติม</string>
<string name="epic_client_missing">ยังไม่ติดตั้ง</string>
<string name="epic_client_open">เปิด</string>
<string name="epic_client_install">ติดตั้ง</string>
<string name="game_tab_about">เกี่ยวกับ</string>
<string name="game_tab_install">ติดตั้ง</string>
<string name="game_install_location">ตำแหน่ง</string>
<string name="game_install_folder">โฟลเดอร์</string>
<string name="game_install_size">ขนาดบนดิสก์</string>
<string name="game_install_date">ติดตั้งแล้ว</string>
<string name="stores_xbox_update">อัปเดต / ตรวจสอบ</string>
<string name="stores_xbox_manage">จัดการเนื้อหา</string>
<string name="stores_xbox_close_first">ปิดเกม Xbox นี้ก่อนอัปเดต</string>
<string name="stores_xbox_content_title">เนื้อหา Xbox</string>
<string name="stores_xbox_content_none">ไม่มีแพ็กเกจเนื้อหา PC ที่มีลิขสิทธิ์สำหรับเกมนี้</string>
<string name="stores_xbox_content_update">%1$s · อัปเดต / ตรวจสอบ</string>
<string name="stores_xbox_content_install">%1$s · ติดตั้ง</string>
<string name="stores_xbox_choose_content">เลือกเนื้อหา PC</string>
<string name="stores_eyebrow_pass">มีให้เล่นกับ %1$s</string>
<string name="stores_owned_in_library">เกมที่เป็นเจ้าของในไลบรารี</string>
<string name="library_filter_added">ที่เพิ่มเอง</string>
<string name="game_tab_launch">การเปิดเกม</string>
<string name="game_tab_saves_cloud">เซฟและคลาวด์</string>
<string name="game_launch_shortcut">ทางลัด Steam</string>
<string name="game_install_version">เวอร์ชัน</string>
<string name="game_install_played">เล่นล่าสุด</string>
<string name="game_install_access">สิทธิ์เข้าถึง</string>
<string name="game_install_access_owned">เป็นเจ้าของ</string>
<string name="game_stream">สตรีม</string>
<string name="library_add">เพิ่มในไลบรารี</string>
<string name="library_remove">นำออกจากไลบรารี</string>
<string name="library_in">อยู่ในไลบรารี</string>
<string name="xbox_filter_show">แสดง</string>
<string name="xbox_filter_cloud">คลาวด์</string>
<string name="stores_xbox_cloud_settings">การตั้งค่าคลาวด์</string>
</resources>
+35 -9
View File
@@ -1015,8 +1015,6 @@
<string name="games_empty">Steam\'de yüklediğiniz oyunlar oturumdan sonra burada görünür.</string>
<string name="games_play_steam">Steam\'de oyna</string>
<string name="games_play_steamvr">SteamVR\'da oyna</string>
<string name="games_launch_settings">Başlatma ayarları</string>
<string name="games_launch">Başlat</string>
<string name="games_files">Oyun dosyaları</string>
<string name="games_prefix">Proton öneki</string>
<string name="games_saves_looking">Kayıtlar aranıyor…</string>
@@ -1024,7 +1022,6 @@
<string name="games_saves_folders">%1$d kayıt klasörü</string>
<string name="games_saves_summary">%1$s · %2$d dosya · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Kayıtları yönet</string>
<string name="games_saves">Oyun kayıtları</string>
<string name="games_import_saves">Kayıtları içe aktar…</string>
<string name="games_export_gamehub">GameHub zip\'i olarak dışa aktar…</string>
@@ -1036,7 +1033,6 @@
<string name="games_runtime_first_play">Çalışma ortamı ilk Oyna\'da yüklenir</string>
<string name="games_runtime_update">Çalışma ortamı güncellemesi var</string>
<string name="games_runtime_ready">● Çalışma ortamı hazır</string>
<string name="games_last_played">Son oynanma %1$s</string>
<string name="games_internal">Dahili depolama</string>
<string name="games_added">Eklenen oyun</string>
<string name="games_custom">Özel oyun</string>
@@ -1105,7 +1101,7 @@
<string name="second_terminal">Linux terminali</string>
<string name="second_terminal_not_ready">Linux oturumu henüz terminal için hazır değil.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Oyunlar</string>
<string name="rail_games">Kütüphane</string>
<string name="rail_desktop">Masaüstü</string>
<string name="rail_store">Mağaza</string>
<string name="rail_components">Bileşenler</string>
@@ -1941,7 +1937,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Özel</string>
<string name="setup_stores">Mağazalar</string>
<string name="setup_stores_open_on">Bir mağazayı şurada aç</string>
<string name="setup_stores_speed">İndirme hızı katmanı</string>
<string name="setup_stores_engine">Mağazalar motoru</string>
<string name="setup_stores_engine_chip_ready">● Motor hazır</string>
@@ -1955,11 +1950,9 @@
<string name="stores_tabs_arrange">Sekmeleri düzenle</string>
<string name="stores_tabs_arrange_done">Bitti</string>
<string name="stores_chip_signed_out">(oturum açılmadı)</string>
<string name="stores_tab_store">Mağaza</string>
<string name="stores_tab_library">Kütüphane</string>
<string name="stores_tab_all">Tümü</string>
<string name="stores_tab_downloads">İndirmeler</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Oturum aç</string>
<string name="stores_signout_named">%1$s · oturumu kapat</string>
<string name="stores_signout_title">%1$s\'ten oturum kapatılsın mı?</string>
@@ -1977,7 +1970,6 @@
<string name="stores_shelf_deals">Fırsatlar</string>
<string name="stores_shelf_free">Ücretsiz oyunlar</string>
<string name="stores_shelf_trending">Gündemde</string>
<string name="stores_shelf_library">Kütüphaneniz</string>
<string name="stores_play">Oyna</string>
<string name="stores_install">Yükle</string>
<string name="stores_resume_install">Yüklemeyi sürdür</string>
@@ -3010,4 +3002,38 @@
<string name="tracking_recovery_look">İyi aydınlatılmış bir odada yavaşça etrafa bakın.\nİzleme kameralarını temiz tutun ve mobilyalara veya diğer görünür ayrıntılara doğru bakın.</string>
<string name="spatial_need_views">Sabit kalibrasyon hedefinin en az 24 farklı görünümünü yakalayın.</string>
<string name="spatial_need_rotation">Akıllı gözlükleri sola ve sağa çevirin, yukarı ve aşağı eğin; dönme olmadan hareket, kamera hizalamasını kalibre edemez.</string>
<string name="epic_client_missing">Yüklü değil</string>
<string name="epic_client_open">Aç</string>
<string name="epic_client_install">Yükle</string>
<string name="game_tab_about">Hakkında</string>
<string name="game_tab_install">Yükle</string>
<string name="game_install_location">Konum</string>
<string name="game_install_folder">Klasör</string>
<string name="game_install_size">Diskteki boyut</string>
<string name="game_install_date">Yüklü</string>
<string name="stores_xbox_update">Güncelle / doğrula</string>
<string name="stores_xbox_manage">İçeriği yönet</string>
<string name="stores_xbox_close_first">Güncellemeden önce bu Xbox oyununu kapatın.</string>
<string name="stores_xbox_content_title">Xbox içeriği</string>
<string name="stores_xbox_content_none">Bu oyun için lisanslı PC içerik paketi yok.</string>
<string name="stores_xbox_content_update">%1$s · Güncelle / doğrula</string>
<string name="stores_xbox_content_install">%1$s · Yükle</string>
<string name="stores_xbox_choose_content">PC içeriği seçin</string>
<string name="stores_eyebrow_pass">%1$s ile kullanılabilir</string>
<string name="stores_owned_in_library">Kütüphane içindeki oyunlarınız</string>
<string name="library_filter_added">Eklenenler</string>
<string name="game_tab_launch">Başlatma</string>
<string name="game_tab_saves_cloud">Kayıtlar ve bulut</string>
<string name="game_launch_shortcut">Steam kısayolu</string>
<string name="game_install_version">Sürüm</string>
<string name="game_install_played">Son oynanma</string>
<string name="game_install_access">Erişim</string>
<string name="game_install_access_owned">Sahip olunan</string>
<string name="game_stream">Akış</string>
<string name="library_add">Kütüphane içine ekle</string>
<string name="library_remove">Kütüphane içinden kaldır</string>
<string name="library_in">Kütüphane içinde</string>
<string name="xbox_filter_show">Göster</string>
<string name="xbox_filter_cloud">Bulut</string>
<string name="stores_xbox_cloud_settings">Bulut ayarları</string>
</resources>
+35 -9
View File
@@ -1035,8 +1035,6 @@
<string name="games_empty">Ігри, які ви встановите в Steam, з’являться тут після сеансу.</string>
<string name="games_play_steam">Грати в Steam</string>
<string name="games_play_steamvr">Грати в SteamVR</string>
<string name="games_launch_settings">Налаштування запуску</string>
<string name="games_launch">Запустити</string>
<string name="games_files">Файли гри</string>
<string name="games_prefix">Префікс Proton</string>
<string name="games_saves_looking">Пошук збережень…</string>
@@ -1044,7 +1042,6 @@
<string name="games_saves_folders">%1$d папок збережень</string>
<string name="games_saves_summary">%1$s · %2$d файлів · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Керування збереженнями</string>
<string name="games_saves">Збереження гри</string>
<string name="games_import_saves">Імпортувати збереження…</string>
<string name="games_export_gamehub">Експортувати як zip GameHub…</string>
@@ -1056,7 +1053,6 @@
<string name="games_runtime_first_play">Середовище встановлюється під час першого натискання «Грати»</string>
<string name="games_runtime_update">Є оновлення середовища</string>
<string name="games_runtime_ready">● Середовище готове</string>
<string name="games_last_played">Останній раз грали %1$s</string>
<string name="games_internal">Внутрішнє сховище</string>
<string name="games_added">Додана гра</string>
<string name="games_custom">Власна гра</string>
@@ -1125,7 +1121,7 @@
<string name="second_terminal">Термінал Linux</string>
<string name="second_terminal_not_ready">Сеанс Linux ще не готовий до роботи з терміналом.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Ігри</string>
<string name="rail_games">Бібліотека</string>
<string name="rail_desktop">Робочий стіл</string>
<string name="rail_store">Магазин</string>
<string name="rail_components">Компоненти</string>
@@ -1989,7 +1985,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Власна</string>
<string name="setup_stores">Магазини</string>
<string name="setup_stores_open_on">Відкривати магазин на</string>
<string name="setup_stores_speed">Рівень швидкості завантаження</string>
<string name="setup_stores_engine">Рушій магазинів</string>
<string name="setup_stores_engine_chip_ready">● Рушій готовий</string>
@@ -2003,11 +1998,9 @@
<string name="stores_tabs_arrange">Упорядкувати вкладки</string>
<string name="stores_tabs_arrange_done">Готово</string>
<string name="stores_chip_signed_out">(вхід не виконано)</string>
<string name="stores_tab_store">Магазин</string>
<string name="stores_tab_library">Бібліотека</string>
<string name="stores_tab_all">Усі</string>
<string name="stores_tab_downloads">Завантаження</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Увійти</string>
<string name="stores_signout_named">%1$s · вийти</string>
<string name="stores_signout_title">Вийти з %1$s?</string>
@@ -2025,7 +2018,6 @@
<string name="stores_shelf_deals">Знижки</string>
<string name="stores_shelf_free">Безкоштовні ігри</string>
<string name="stores_shelf_trending">Популярне</string>
<string name="stores_shelf_library">Ваша бібліотека</string>
<string name="stores_play">Грати</string>
<string name="stores_install">Встановити</string>
<string name="stores_resume_install">Продовжити встановлення</string>
@@ -3076,4 +3068,38 @@
<string name="spatial_model_checksum_error">Невідповідність контрольної суми моделі руки</string>
<string name="spatial_opencv_load_error">Не вдалося завантажити OpenCV</string>
<string name="spatial_gpu_hint">Типово використовується CPU. Режим GPU є експериментальним і може спричинити перезапуск SteamVR. Вибір скидається після перезапуску відстеження.</string>
<string name="epic_client_missing">Не встановлено</string>
<string name="epic_client_open">Відкрити</string>
<string name="epic_client_install">Установити</string>
<string name="game_tab_about">Про додаток</string>
<string name="game_tab_install">Установити</string>
<string name="game_install_location">Розташування</string>
<string name="game_install_folder">Папка</string>
<string name="game_install_size">Розмір на диску</string>
<string name="game_install_date">Встановлено</string>
<string name="stores_xbox_update">Оновити / перевірити</string>
<string name="stores_xbox_manage">Керувати вмістом</string>
<string name="stores_xbox_close_first">Закрийте цю гру Xbox перед оновленням.</string>
<string name="stores_xbox_content_title">Вміст Xbox</string>
<string name="stores_xbox_content_none">Для цієї гри немає ліцензованих пакетів вмісту для ПК.</string>
<string name="stores_xbox_content_update">%1$s · Оновити / перевірити</string>
<string name="stores_xbox_content_install">%1$s · Установити</string>
<string name="stores_xbox_choose_content">Виберіть вміст для ПК</string>
<string name="stores_eyebrow_pass">доступно з %1$s</string>
<string name="stores_owned_in_library">Ваші ігри в бібліотеці</string>
<string name="library_filter_added">Додані</string>
<string name="game_tab_launch">Запуск</string>
<string name="game_tab_saves_cloud">Збереження й хмара</string>
<string name="game_launch_shortcut">Ярлик Steam</string>
<string name="game_install_version">Версія</string>
<string name="game_install_played">Остання гра</string>
<string name="game_install_access">Доступ</string>
<string name="game_install_access_owned">Придбана</string>
<string name="game_stream">Стримінг</string>
<string name="library_add">Додати до бібліотеки</string>
<string name="library_remove">Прибрати з бібліотеки</string>
<string name="library_in">У бібліотеці</string>
<string name="xbox_filter_show">Показати</string>
<string name="xbox_filter_cloud">Хмара</string>
<string name="stores_xbox_cloud_settings">Налаштування хмари</string>
</resources>
+35 -9
View File
@@ -1017,8 +1017,6 @@
<string name="games_empty">Game bạn cài trong Steam sẽ xuất hiện ở đây sau phiên.</string>
<string name="games_play_steam">Chơi trên Steam</string>
<string name="games_play_steamvr">Chơi trên SteamVR</string>
<string name="games_launch_settings">Thiết lập khởi chạy</string>
<string name="games_launch">Khởi chạy</string>
<string name="games_files">Tệp game</string>
<string name="games_prefix">Prefix Proton</string>
<string name="games_saves_looking">Đang tìm các lần lưu…</string>
@@ -1026,7 +1024,6 @@
<string name="games_saves_folders">%1$d thư mục lưu</string>
<string name="games_saves_summary">%1$s · %2$d tệp · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Quản lý tệp lưu</string>
<string name="games_saves">Tệp lưu game</string>
<string name="games_import_saves">Nhập tệp lưu…</string>
<string name="games_export_gamehub">Xuất dưới dạng zip GameHub…</string>
@@ -1038,7 +1035,6 @@
<string name="games_runtime_first_play">Runtime sẽ cài ở lần Play đầu tiên</string>
<string name="games_runtime_update">Có cập nhật runtime</string>
<string name="games_runtime_ready">● Runtime đã sẵn sàng</string>
<string name="games_last_played">Chơi lần cuối %1$s</string>
<string name="games_internal">Bộ nhớ trong</string>
<string name="games_added">Game đã thêm</string>
<string name="games_custom">Game tùy chỉnh</string>
@@ -1107,7 +1103,7 @@
<string name="second_terminal">Terminal Linux</string>
<string name="second_terminal_not_ready">Phiên Linux chưa sẵn sàng cho terminal.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Games</string>
<string name="rail_games">Thư viện</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Cửa hàng</string>
<string name="rail_components">Thành phần</string>
@@ -1926,7 +1922,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Tùy chỉnh</string>
<string name="setup_stores">Cửa hàng</string>
<string name="setup_stores_open_on">Mở một store trên</string>
<string name="setup_stores_speed">Mức tốc độ tải</string>
<string name="setup_stores_engine">Engine stores</string>
<string name="setup_stores_engine_chip_ready">● Engine sẵn sàng</string>
@@ -1940,11 +1935,9 @@
<string name="stores_tabs_arrange">Sắp xếp thẻ</string>
<string name="stores_tabs_arrange_done">Xong</string>
<string name="stores_chip_signed_out">(chưa đăng nhập)</string>
<string name="stores_tab_store">Cửa hàng</string>
<string name="stores_tab_library">Thư viện</string>
<string name="stores_tab_all">Tất cả</string>
<string name="stores_tab_downloads">Tải xuống</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Đăng nhập</string>
<string name="stores_signout_named">%1$s · đăng xuất</string>
<string name="stores_signout_title">Đăng xuất khỏi %1$s?</string>
@@ -1962,7 +1955,6 @@
<string name="stores_shelf_deals">Ưu đãi</string>
<string name="stores_shelf_free">Game miễn phí</string>
<string name="stores_shelf_trending">Xu hướng</string>
<string name="stores_shelf_library">Thư viện của bạn</string>
<string name="stores_play">Chơi</string>
<string name="stores_install">Cài</string>
<string name="stores_resume_install">Tiếp tục cài</string>
@@ -2983,4 +2975,38 @@
<string name="spatial_model_checksum_error">Tổng kiểm tra mô hình bàn tay không khớp</string>
<string name="spatial_opencv_load_error">OpenCV không thể tải</string>
<string name="tracking_recovery_look">Nhìn xung quanh chậm rãi trong phòng có đủ ánh sáng.\nGiữ cho camera theo dõi rõ ràng và hướng về phía đồ nội thất hoặc các chi tiết có thể nhìn thấy khác.</string>
<string name="epic_client_missing">Chưa cài</string>
<string name="epic_client_open">Mở</string>
<string name="epic_client_install">Cài đặt</string>
<string name="game_tab_about">Giới thiệu</string>
<string name="game_tab_install">Cài đặt</string>
<string name="game_install_location">Vị trí</string>
<string name="game_install_folder">Thư mục</string>
<string name="game_install_size">Dung lượng trên đĩa</string>
<string name="game_install_date">Đã cài</string>
<string name="stores_xbox_update">Cập nhật / xác minh</string>
<string name="stores_xbox_manage">Quản lý nội dung</string>
<string name="stores_xbox_close_first">Hãy đóng trò chơi Xbox này trước khi cập nhật.</string>
<string name="stores_xbox_content_title">Nội dung Xbox</string>
<string name="stores_xbox_content_none">Không có gói nội dung PC có giấy phép cho trò chơi này.</string>
<string name="stores_xbox_content_update">%1$s · Cập nhật / xác minh</string>
<string name="stores_xbox_content_install">%1$s · Cài đặt</string>
<string name="stores_xbox_choose_content">Chọn nội dung PC</string>
<string name="stores_eyebrow_pass">có trong %1$s</string>
<string name="stores_owned_in_library">Trò chơi đã sở hữu trong Thư viện</string>
<string name="library_filter_added">Đã thêm</string>
<string name="game_tab_launch">Khởi chạy</string>
<string name="game_tab_saves_cloud">Bản lưu và đám mây</string>
<string name="game_launch_shortcut">Lối tắt Steam</string>
<string name="game_install_version">Phiên bản</string>
<string name="game_install_played">Chơi lần cuối</string>
<string name="game_install_access">Quyền truy cập</string>
<string name="game_install_access_owned">Đã sở hữu</string>
<string name="game_stream">Phát trực tuyến</string>
<string name="library_add">Thêm vào Thư viện</string>
<string name="library_remove">Xóa khỏi Thư viện</string>
<string name="library_in">Trong Thư viện</string>
<string name="xbox_filter_show">Hiển thị</string>
<string name="xbox_filter_cloud">Đám mây</string>
<string name="stores_xbox_cloud_settings">Cài đặt đám mây</string>
</resources>
+35 -9
View File
@@ -1101,8 +1101,6 @@
<string name="games_empty">你在 Steam 安装的游戏会在会话结束后显示在这里。</string>
<string name="games_play_steam">游玩 Steam</string>
<string name="games_play_steamvr">游玩 SteamVR</string>
<string name="games_launch_settings">启动设置</string>
<string name="games_launch">启动</string>
<string name="games_files">游戏文件</string>
<string name="games_prefix">Proton 前缀</string>
<string name="games_saves_looking">正在查找存档…</string>
@@ -1110,7 +1108,6 @@
<string name="games_saves_folders">%1$d 个存档文件夹</string>
<string name="games_saves_summary">%1$s · %2$d 个文件 · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">管理存档</string>
<string name="games_saves">游戏存档</string>
<string name="games_import_saves">导入存档…</string>
<string name="games_export_gamehub">导出为 GameHub zip 包…</string>
@@ -1122,7 +1119,6 @@
<string name="games_runtime_first_play">运行时在首次“游玩”时安装</string>
<string name="games_runtime_update">运行时有可用更新</string>
<string name="games_runtime_ready">● 运行时就绪</string>
<string name="games_last_played">上次游玩于 %1$s</string>
<string name="games_internal">内部存储</string>
<string name="games_added">已添加游戏</string>
<string name="games_custom">自定义游戏</string>
@@ -1191,7 +1187,7 @@
<string name="second_terminal">Linux 终端</string>
<string name="second_terminal_not_ready">Linux 会话尚未准备好使用终端。</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">游戏</string>
<string name="rail_games">库</string>
<string name="rail_desktop">桌面</string>
<string name="rail_store">商店</string>
<string name="rail_components">组件</string>
@@ -2005,7 +2001,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">自定义</string>
<string name="setup_stores">游戏商店</string>
<string name="setup_stores_open_on">打开商店时显示</string>
<string name="setup_stores_speed">下载速度档位</string>
<string name="setup_stores_engine">商店引擎</string>
<string name="setup_stores_engine_chip_ready">● 引擎就绪</string>
@@ -2019,11 +2014,9 @@
<string name="stores_tabs_arrange">整理标签</string>
<string name="stores_tabs_arrange_done">完成</string>
<string name="stores_chip_signed_out">(未登录)</string>
<string name="stores_tab_store">商店</string>
<string name="stores_tab_library">库</string>
<string name="stores_tab_all">全部</string>
<string name="stores_tab_downloads">下载</string>
<string name="stores_tab_count">%1$s(%2$d)</string>
<string name="stores_signin">登录</string>
<string name="stores_signout_named">%1$s · 退出登录</string>
<string name="stores_signout_title">要从 %1$s 退出登录吗?</string>
@@ -2041,7 +2034,6 @@
<string name="stores_shelf_deals">特惠</string>
<string name="stores_shelf_free">免费游戏</string>
<string name="stores_shelf_trending">趋势</string>
<string name="stores_shelf_library">你的库</string>
<string name="stores_play">游玩</string>
<string name="stores_install">安装</string>
<string name="stores_resume_install">继续安装</string>
@@ -2986,4 +2978,38 @@
<string name="glasses_6dof">6DoF 虚拟屏幕</string>
<string name="tracking_recovery_look">在光线充足的房间内缓慢环顾四周。\n不要遮挡追踪摄像头,并面向家具等有明显细节的物体。</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">未安装</string>
<string name="epic_client_open">打开</string>
<string name="epic_client_install">安装</string>
<string name="game_tab_about">关于</string>
<string name="game_tab_install">安装</string>
<string name="game_install_location">位置</string>
<string name="game_install_folder">文件夹</string>
<string name="game_install_size">占用空间</string>
<string name="game_install_date">已安装</string>
<string name="stores_xbox_update">更新 / 验证</string>
<string name="stores_xbox_manage">管理内容</string>
<string name="stores_xbox_close_first">更新前请先关闭此 Xbox 游戏。</string>
<string name="stores_xbox_content_title">Xbox 内容</string>
<string name="stores_xbox_content_none">此游戏没有已授权的 PC 内容包。</string>
<string name="stores_xbox_content_update">%1$s · 更新 / 验证</string>
<string name="stores_xbox_content_install">%1$s · 安装</string>
<string name="stores_xbox_choose_content">选择 PC 内容</string>
<string name="stores_eyebrow_pass">%1$s 可玩</string>
<string name="stores_owned_in_library">游戏库中已拥有的游戏</string>
<string name="library_filter_added">已添加</string>
<string name="game_tab_launch">启动</string>
<string name="game_tab_saves_cloud">存档和云</string>
<string name="game_launch_shortcut">Steam 快捷方式</string>
<string name="game_install_version">版本</string>
<string name="game_install_played">上次游玩</string>
<string name="game_install_access">访问权限</string>
<string name="game_install_access_owned">已拥有</string>
<string name="game_stream">串流</string>
<string name="library_add">添加到游戏库</string>
<string name="library_remove">从游戏库移除</string>
<string name="library_in">已在游戏库</string>
<string name="xbox_filter_show">显示</string>
<string name="xbox_filter_cloud">云</string>
<string name="stores_xbox_cloud_settings">云设置</string>
</resources>
+35 -9
View File
@@ -1019,8 +1019,6 @@
<string name="games_empty">你在 Steam 安裝的遊戲會在會話結束後顯示在這裡。</string>
<string name="games_play_steam">開啟 Steam</string>
<string name="games_play_steamvr">開啟 SteamVR</string>
<string name="games_launch_settings">啟動設定</string>
<string name="games_launch">啟動</string>
<string name="games_files">遊戲檔案</string>
<string name="games_prefix">Proton 前綴</string>
<string name="games_saves_looking">正在尋找存檔…</string>
@@ -1028,7 +1026,6 @@
<string name="games_saves_folders">%1$d 個存檔資料夾</string>
<string name="games_saves_summary">%1$s · %2$d 個檔案 · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">管理存檔</string>
<string name="games_saves">遊戲存檔</string>
<string name="games_import_saves">匯入存檔…</string>
<string name="games_export_gamehub">匯出為 GameHub zip…</string>
@@ -1040,7 +1037,6 @@
<string name="games_runtime_first_play">首次開啟 Steam 時安裝運行環境</string>
<string name="games_runtime_update">運行環境有可用更新</string>
<string name="games_runtime_ready">● 運行環境已就緒</string>
<string name="games_last_played">上次遊玩:%1$s</string>
<string name="games_internal">內部儲存空間</string>
<string name="games_added">已新增的遊戲</string>
<string name="games_custom">自訂遊戲</string>
@@ -1109,7 +1105,7 @@
<string name="second_terminal">Linux 終端機</string>
<string name="second_terminal_not_ready">Linux 會話尚未準備好使用終端機。</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">遊戲</string>
<string name="rail_games">收藏庫</string>
<string name="rail_desktop">桌面</string>
<string name="rail_store">商店</string>
<string name="rail_components">組件</string>
@@ -1929,7 +1925,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">自訂</string>
<string name="setup_stores">商店</string>
<string name="setup_stores_open_on">開啟商店時顯示</string>
<string name="setup_stores_speed">下載速度等級</string>
<string name="setup_stores_engine">商店引擎</string>
<string name="setup_stores_engine_chip_ready">● 引擎已就緒</string>
@@ -1943,11 +1938,9 @@
<string name="stores_tabs_arrange">整理標籤</string>
<string name="stores_tabs_arrange_done">完成</string>
<string name="stores_chip_signed_out">(未登入)</string>
<string name="stores_tab_store">商店</string>
<string name="stores_tab_library">收藏庫</string>
<string name="stores_tab_all">全部</string>
<string name="stores_tab_downloads">下載</string>
<string name="stores_tab_count">%1$s(%2$d)</string>
<string name="stores_signin">登入</string>
<string name="stores_signout_named">%1$s · 登出</string>
<string name="stores_signout_title">登出 %1$s?</string>
@@ -1965,7 +1958,6 @@
<string name="stores_shelf_deals">優惠</string>
<string name="stores_shelf_free">免費遊戲</string>
<string name="stores_shelf_trending">趨勢</string>
<string name="stores_shelf_library">你的收藏庫</string>
<string name="stores_play">開啟遊戲</string>
<string name="stores_install">安裝</string>
<string name="stores_resume_install">繼續安裝</string>
@@ -2986,4 +2978,38 @@
<string name="glasses_6dof">6DoF 虛擬螢幕</string>
<string name="tracking_recovery_look">在光線充足的房間內緩慢環顧四周。\n不要遮擋追蹤攝影機,並面向家具等有明顯細節的物體。</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">未安裝</string>
<string name="epic_client_open">開啟</string>
<string name="epic_client_install">安裝</string>
<string name="game_tab_about">關於</string>
<string name="game_tab_install">安裝</string>
<string name="game_install_location">位置</string>
<string name="game_install_folder">資料夾</string>
<string name="game_install_size">佔用空間</string>
<string name="game_install_date">已安裝</string>
<string name="stores_xbox_update">更新 / 驗證</string>
<string name="stores_xbox_manage">管理內容</string>
<string name="stores_xbox_close_first">更新前請先關閉此 Xbox 遊戲。</string>
<string name="stores_xbox_content_title">Xbox 內容</string>
<string name="stores_xbox_content_none">此遊戲沒有已授權的 PC 內容套件。</string>
<string name="stores_xbox_content_update">%1$s · 更新 / 驗證</string>
<string name="stores_xbox_content_install">%1$s · 安裝</string>
<string name="stores_xbox_choose_content">選擇 PC 內容</string>
<string name="stores_eyebrow_pass">%1$s 可玩</string>
<string name="stores_owned_in_library">收藏庫中已擁有的遊戲</string>
<string name="library_filter_added">已加入</string>
<string name="game_tab_launch">啟動</string>
<string name="game_tab_saves_cloud">存檔及雲端</string>
<string name="game_launch_shortcut">Steam 捷徑</string>
<string name="game_install_version">版本</string>
<string name="game_install_played">上次遊玩</string>
<string name="game_install_access">存取權</string>
<string name="game_install_access_owned">已擁有</string>
<string name="game_stream">串流</string>
<string name="library_add">加入收藏庫</string>
<string name="library_remove">從收藏庫移除</string>
<string name="library_in">已在收藏庫</string>
<string name="xbox_filter_show">顯示</string>
<string name="xbox_filter_cloud">雲端</string>
<string name="stores_xbox_cloud_settings">雲端設定</string>
</resources>
+35 -9
View File
@@ -1019,8 +1019,6 @@
<string name="games_empty">在 Steam 中安裝的遊戲會在工作階段結束後顯示在這裡。</string>
<string name="games_play_steam">執行 Steam</string>
<string name="games_play_steamvr">執行 SteamVR</string>
<string name="games_launch_settings">啟動設定</string>
<string name="games_launch">啟動</string>
<string name="games_files">遊戲檔案</string>
<string name="games_prefix">Proton 前綴</string>
<string name="games_saves_looking">正在尋找存檔…</string>
@@ -1028,7 +1026,6 @@
<string name="games_saves_folders">%1$d 個存檔資料夾</string>
<string name="games_saves_summary">%1$s · %2$d 個檔案 · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">管理存檔</string>
<string name="games_saves">遊戲存檔</string>
<string name="games_import_saves">匯入存檔…</string>
<string name="games_export_gamehub">匯出為 GameHub 壓縮檔…</string>
@@ -1040,7 +1037,6 @@
<string name="games_runtime_first_play">首次點擊「執行」時安裝執行環境</string>
<string name="games_runtime_update">執行環境有可用更新</string>
<string name="games_runtime_ready">● 執行環境已就緒</string>
<string name="games_last_played">上次執行 %1$s</string>
<string name="games_internal">內部儲存空間</string>
<string name="games_added">已新增的遊戲</string>
<string name="games_custom">自訂遊戲</string>
@@ -1109,7 +1105,7 @@
<string name="second_terminal">Linux 終端機</string>
<string name="second_terminal_not_ready">Linux 工作階段尚未準備好,無法使用終端機。</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">遊戲</string>
<string name="rail_games">收藏庫</string>
<string name="rail_desktop">桌面</string>
<string name="rail_store">商店</string>
<string name="rail_components">元件</string>
@@ -1929,7 +1925,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">自訂</string>
<string name="setup_stores">遊戲商店</string>
<string name="setup_stores_open_on">開啟商店時顯示</string>
<string name="setup_stores_speed">下載速度等級</string>
<string name="setup_stores_engine">商店引擎</string>
<string name="setup_stores_engine_chip_ready">● 引擎已就緒</string>
@@ -1943,11 +1938,9 @@
<string name="stores_tabs_arrange">整理分頁</string>
<string name="stores_tabs_arrange_done">完成</string>
<string name="stores_chip_signed_out">(未登入)</string>
<string name="stores_tab_store">商店</string>
<string name="stores_tab_library">收藏庫</string>
<string name="stores_tab_all">全部</string>
<string name="stores_tab_downloads">下載</string>
<string name="stores_tab_count">%1$s(%2$d)</string>
<string name="stores_signin">登入</string>
<string name="stores_signout_named">%1$s · 登出</string>
<string name="stores_signout_title">要登出 %1$s 嗎?</string>
@@ -1965,7 +1958,6 @@
<string name="stores_shelf_deals">特惠</string>
<string name="stores_shelf_free">免費遊戲</string>
<string name="stores_shelf_trending">趨勢</string>
<string name="stores_shelf_library">你的收藏庫</string>
<string name="stores_play">執行</string>
<string name="stores_install">安裝</string>
<string name="stores_resume_install">繼續安裝</string>
@@ -2986,4 +2978,38 @@
<string name="glasses_6dof">6DoF 虛擬螢幕</string>
<string name="tracking_recovery_look">在光線充足的房間內緩慢環顧四周。\n不要遮擋追蹤攝影機,並面向家具等有明顯細節的物體。</string>
<string name="spatial_print_job">DroidDeck ChArUco 30 mm</string>
<string name="epic_client_missing">未安裝</string>
<string name="epic_client_open">開啟</string>
<string name="epic_client_install">安裝</string>
<string name="game_tab_about">關於</string>
<string name="game_tab_install">安裝</string>
<string name="game_install_location">位置</string>
<string name="game_install_folder">資料夾</string>
<string name="game_install_size">佔用空間</string>
<string name="game_install_date">已安裝</string>
<string name="stores_xbox_update">更新 / 驗證</string>
<string name="stores_xbox_manage">管理內容</string>
<string name="stores_xbox_close_first">更新前請先關閉這款 Xbox 遊戲。</string>
<string name="stores_xbox_content_title">Xbox 內容</string>
<string name="stores_xbox_content_none">這款遊戲沒有已授權的 PC 內容套件。</string>
<string name="stores_xbox_content_update">%1$s · 更新 / 驗證</string>
<string name="stores_xbox_content_install">%1$s · 安裝</string>
<string name="stores_xbox_choose_content">選擇 PC 內容</string>
<string name="stores_eyebrow_pass">%1$s 可玩</string>
<string name="stores_owned_in_library">收藏庫中已擁有的遊戲</string>
<string name="library_filter_added">已加入</string>
<string name="game_tab_launch">啟動</string>
<string name="game_tab_saves_cloud">存檔與雲端</string>
<string name="game_launch_shortcut">Steam 捷徑</string>
<string name="game_install_version">版本</string>
<string name="game_install_played">上次遊玩</string>
<string name="game_install_access">存取權</string>
<string name="game_install_access_owned">已擁有</string>
<string name="game_stream">串流</string>
<string name="library_add">加入收藏庫</string>
<string name="library_remove">從收藏庫移除</string>
<string name="library_in">已在收藏庫</string>
<string name="xbox_filter_show">顯示</string>
<string name="xbox_filter_cloud">雲端</string>
<string name="stores_xbox_cloud_settings">雲端設定</string>
</resources>
+40 -9
View File
@@ -1069,8 +1069,6 @@
<string name="games_empty">Games you install in Steam show up here after the session.</string>
<string name="games_play_steam">Play Steam</string>
<string name="games_play_steamvr">Play SteamVR</string>
<string name="games_launch_settings">Launch settings</string>
<string name="games_launch">Launch</string>
<string name="games_files">Game files</string>
<string name="games_prefix">Proton prefix</string>
<string name="games_saves_looking">Looking for saves…</string>
@@ -1078,7 +1076,6 @@
<string name="games_saves_folders">%1$d save folders</string>
<string name="games_saves_summary">%1$s · %2$d files · %3$s</string>
<string name="games_kb">%1$d KB</string>
<string name="games_manage_saves">Manage saves</string>
<string name="games_saves">Game saves</string>
<string name="games_import_saves">Import saves…</string>
<string name="games_export_gamehub">Export as GameHub zip…</string>
@@ -1090,7 +1087,6 @@
<string name="games_runtime_first_play">Runtime installs on first Play</string>
<string name="games_runtime_update">Runtime update available</string>
<string name="games_runtime_ready">● Runtime ready</string>
<string name="games_last_played">Last played %1$s</string>
<string name="games_internal">Internal storage</string>
<string name="games_added">Added game</string>
<string name="games_custom">Custom game</string>
@@ -1162,7 +1158,7 @@
<string name="second_terminal">Linux terminal</string>
<string name="second_terminal_not_ready">The Linux session isn\'t ready for a terminal yet.</string>
<string name="rail_steam">Steam</string>
<string name="rail_games">Games</string>
<string name="rail_games">Library</string>
<string name="rail_desktop">Desktop</string>
<string name="rail_store">Store</string>
<string name="rail_components">Components</string>
@@ -2026,7 +2022,6 @@
<string name="source_steam">Steam</string>
<string name="source_added">Custom</string>
<string name="setup_stores">Stores</string>
<string name="setup_stores_open_on">Open a store on</string>
<string name="setup_stores_speed">Download speed tier</string>
<string name="setup_stores_engine">Stores engine</string>
<string name="setup_stores_engine_chip_ready">● Engine ready</string>
@@ -2040,11 +2035,9 @@
<string name="stores_tabs_arrange">Arrange tabs</string>
<string name="stores_tabs_arrange_done">Done</string>
<string name="stores_chip_signed_out">(not signed in)</string>
<string name="stores_tab_store">Store</string>
<string name="stores_tab_library">Library</string>
<string name="stores_tab_all">All</string>
<string name="stores_tab_downloads">Downloads</string>
<string name="stores_tab_count">%1$s (%2$d)</string>
<string name="stores_signin">Sign in</string>
<string name="stores_signout_named">%1$s · sign out</string>
<string name="stores_signout_title">Sign out of %1$s?</string>
@@ -2062,7 +2055,6 @@
<string name="stores_shelf_deals">Deals</string>
<string name="stores_shelf_free">Free games</string>
<string name="stores_shelf_trending">Trending</string>
<string name="stores_shelf_library">Your library</string>
<string name="stores_play">Play</string>
<string name="stores_install">Install</string>
<string name="stores_resume_install">Resume install</string>
@@ -3090,4 +3082,43 @@
<string name="spatial_saved_views_hint">Saved views can be recalculated. Start a new calibration to capture in the current tracking frame.</string>
<string name="spatial_model_checksum_error">Hand model checksum mismatch</string>
<string name="spatial_opencv_load_error">OpenCV could not load</string>
<string name="stores_xbox_update">Update / verify</string>
<string name="stores_xbox_manage">Manage content</string>
<string name="stores_xbox_close_first">Close this Xbox game before updating it.</string>
<string name="stores_xbox_content_title">Xbox content</string>
<string name="stores_xbox_content_none">No licensed PC content packages are available for this game.</string>
<string name="stores_xbox_content_update">%1$s · Update / verify</string>
<string name="stores_xbox_content_install">%1$s · Install</string>
<string name="stores_xbox_choose_content">Choose PC content</string>
<string name="stores_xbox_choice" translatable="false">%1$s (%2$s)</string>
<string name="stores_game_pass" translatable="false">PC Game Pass</string>
<string name="stores_eyebrow_pass">available with %1$s</string>
<string name="stores_owned_in_library">Owned games in Library</string>
<string name="epic_client_missing">Not installed</string>
<string name="epic_client_open">Open</string>
<string name="epic_client_install">Install</string>
<string name="library_filter_added">Added</string>
<string name="game_tab_about">About</string>
<string name="game_tab_achievements_count" translatable="false">%1$s %2$d/%3$d</string>
<string name="game_tab_launch">Launch</string>
<string name="game_tab_saves_cloud">Saves &amp; cloud</string>
<string name="game_tab_install">Install</string>
<string name="game_launch_shortcut">Steam shortcut</string>
<string name="game_install_location">Location</string>
<string name="game_install_folder">Folder</string>
<string name="game_install_size">Size on disk</string>
<string name="game_install_version">Version</string>
<string name="game_install_date">Installed</string>
<string name="game_install_played">Last played</string>
<string name="game_install_access">Access</string>
<string name="game_install_access_owned">Owned</string>
<string name="game_stream">Stream</string>
<string name="library_add">Add to Library</string>
<string name="library_remove">Remove from Library</string>
<string name="library_in">In Library</string>
<string name="xbox_filter_show">Show</string>
<string name="xbox_filter_cloud">Cloud</string>
<string name="stores_xbox_cloud_settings">Cloud settings</string>
<string name="xbox_cloud_gaming" translatable="false">Xbox Cloud Gaming</string>
<string name="xbox_filter_game_pass" translatable="false">Game Pass</string>
</resources>
@@ -7,6 +7,13 @@ class SortCatalogItemsTest {
private fun item(id: String, title: String, size: Long = 0, releaseDate: String = "") =
CatalogItem(Store.GOG, id, title, imageUrl = null, sizeBytes = size, releaseDate = releaseDate, owned = true)
private fun sortCatalogItems(
items: List<CatalogItem>, sort: String,
lastPlayed: (CatalogItem) -> Long = { 0L }, installedAt: (CatalogItem) -> Long = { 0L },
) = sortGames(items, sort, { it.title }, { it.releaseDate }, lastPlayed, installedAt, { it.sizeBytes })
private fun retainCatalogOrder(items: List<CatalogItem>, previous: List<CatalogItem>) = retainOrder(items, previous) { it.key }
@Test fun titleOrderIsCaseInsensitiveAndDescendingIsItsMirror() {
val items = listOf(item("1", "Zelda"), item("2", "alpha"), item("3", "Beta"))
assertEquals(listOf("alpha", "Beta", "Zelda"), sortCatalogItems(items, "titleAsc").map { it.title })
@@ -16,17 +16,6 @@ import java.security.MessageDigest
class EpicClientTest {
@get:Rule val tmp = TemporaryFolder()
private fun game(appId: Int, source: String) =
com.droiddeck.launcher.frontend.Library.SteamGame(appId, "g$appId", null, "internal", source = source)
@Test fun theLauncherSitsRightAboveTheFirstEpicGame() {
val launcher = game(EpicClient.LAUNCHER_APP_ID, "epic")
val games = listOf(game(1, "steam"), game(2, "epic"), game(3, "steam"), game(4, "epic"))
assertEquals(listOf(1, 0, 2, 3, 4), EpicClient.withLauncher(games, launcher).map { it.appId })
assertEquals(listOf(1, 3, 0), EpicClient.withLauncher(listOf(games[0], games[2]), launcher).map { it.appId })
assertEquals(listOf(1, 2), EpicClient.withLauncher(games.take(2), null).map { it.appId })
}
private fun added(name: String, source: String, storeId: String?) =
com.droiddeck.launcher.frontend.AddedGames.Game(File(tmp.root, name), name, File(tmp.root, "$name/$name.exe"),
"/root/Games/$name/$name.exe", "/root/Games/$name", 3_000_000_000L, 0L, emptyList(), source = source, storeId = storeId)
@@ -48,7 +48,7 @@ class GamesClientTagTest {
DroidDeckTheme {
CompositionLocalProvider(LocalFrontFocus provides FrontFocus(), LocalNarrowPane provides narrow) {
FocusGlideHost {
GamesPage(FrontEndState(
LibraryPage(FrontEndState(
installed = "r9", ready = true, available = null, busy = false, stage = "", percent = -1,
desktopInstalled = true, offlineAccount = null, offline = false, frameGenLabel = "",
romsDir = null, logsEnabled = false, steamGames = games, steamGamesX64 = x64, emulators = emptyList(), running = null,
@@ -60,7 +60,7 @@ class GamesClientTagTest {
onRoms = {}, onFiles = {}, onLogs = {}, onOffline = {},
onWirelessAdbPair = { _, _, _, _ -> }, onWirelessAdbApply = { _, _, _, _ -> },
onSetPhantomProcessLimit = { _, _ -> },
), selected = "app:440", onSelect = {}, modifier = Modifier.fillMaxSize())
), selected = "games", onSelect = {}, modifier = Modifier.fillMaxSize())
}
}
}
@@ -0,0 +1,51 @@
package com.droiddeck.launcher.ui
import com.droiddeck.launcher.frontend.Library
import com.droiddeck.launcher.stores.CatalogItem
import com.droiddeck.launcher.stores.Store
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Test
class LibraryEntriesTest {
private fun owned(store: Store, id: String, title: String = id) = CatalogItem(store, id, title, imageUrl = null, owned = true)
@Test fun anInstalledStoreGameClaimsItsOwnedProductSoItShowsOnce() {
val games = listOf(
Library.SteamGame(440, "Team Fortress 2", null, "Steam library"),
Library.SteamGame(-5, "Cyberpunk", null, Library.ADDED, source = "gog", storeId = "1423049311"),
)
val library = mapOf(Store.GOG to listOf(owned(Store.GOG, "1423049311", "Cyberpunk 2077"), owned(Store.GOG, "1207658924", "Witcher")))
val entries = libraryEntries(games, library, emptySet())
assertEquals(listOf("app:440", "app:-5", "app:gog:1207658924"), entries.map { it.key })
assertEquals("gog:1423049311", entries[1].item?.key)
assertTrue(entries[1].installed)
assertFalse(entries[2].installed)
assertNull(entries[2].game)
}
@Test fun theEpicLauncherIsNotAGame() {
val launcher = Library.SteamGame(0, Library.EPIC_LAUNCHER_NAME, null, Library.EPIC_LAUNCHER)
assertEquals(emptyList<String>(), libraryEntries(listOf(launcher), emptyMap(), emptySet()).map { it.key })
}
@Test fun aStoreInstallTheLibraryHasNotScannedYetCountsAsInstalled() {
val entries = libraryEntries(emptyList(), mapOf(Store.EPIC to listOf(owned(Store.EPIC, "fn"))), setOf("epic:fn"))
assertTrue(entries.single().installed)
}
@Test fun installedGamesComeFirstKeepingTheirSortedOrder() {
val entries = listOf("a", "b", "c", "d").map { LibraryEntry(it, "steam", it, null, null, installed = it == "b" || it == "d") }
assertEquals(listOf("b", "d", "a", "c"), installedFirst(entries).map { it.key })
}
@Test fun gameKeysNameEitherAnAppOrAStoreProduct() {
GameRef.parse("app:440").let { assertEquals(440, it.appId); assertNull(it.store) }
GameRef.parse("app:-12").let { assertEquals(-12, it.appId) }
GameRef.parse("app:gog:1207658924").let { assertEquals(Store.GOG, it.store); assertEquals("1207658924", it.storeId); assertNull(it.appId) }
GameRef.parse("xbox:9NBLGGH4R315").let { assertEquals(Store.XBOX, it.store); assertEquals("9NBLGGH4R315", it.storeId) }
GameRef.parse("app:nope:1").let { assertNull(it.store); assertNull(it.appId) }
}
}
@@ -46,9 +46,4 @@ class StoreGridNavigationTest {
assertEquals(10, storeGridNeighbor(6, 11, 4, 1, split = 6))
assertNull(storeGridNeighbor(1, 11, 4, -1, split = 6))
}
@Test fun installedGamesComeFirstKeepingTheirSortedOrder() {
val items = listOf("a", "b", "c", "d").map { com.droiddeck.launcher.stores.CatalogItem(com.droiddeck.launcher.stores.Store.GOG, it, it, null) }
assertEquals(listOf("b", "d", "a", "c"), installedFirst(items, setOf("d", "b")).map { it.id })
}
}
@@ -1,17 +0,0 @@
package com.droiddeck.launcher.ui
import com.droiddeck.launcher.stores.Store
import org.junit.Assert.assertEquals
import org.junit.Test
class StoreTabsTest {
@Test fun libraryAndStoreAmazonLibraryOnly() {
assertEquals(listOf("library", "store"), tabsFor(Store.GOG))
assertEquals(listOf("library", "store"), tabsFor(Store.ITCH))
assertEquals(listOf("library"), tabsFor(Store.AMAZON))
assertEquals("library", tabFor(Store.AMAZON, "store"))
assertEquals("library", tabFor(Store.GOG, "all"))
assertEquals("library", tabFor(Store.EPIC, "installed"))
assertEquals("store", tabFor(Store.GOG, "store"))
}
}
+5 -2
View File
@@ -338,8 +338,11 @@ Automator show them:
| Tag | Where |
|---|---|
| `rail-steam`, `rail-games`, `rail-desktop`, `rail-store`, `rail-components`, `rail-android-apps`, `rail-setup`, `rail-updates` | the front end's rail |
| `play-steam` | Play on the Steam and Games pages |
| `rail-steam`, `rail-games` (the Library), `rail-desktop`, `rail-store`, `rail-components`, `rail-android-apps`, `rail-setup`, `rail-updates` | the front end's rail |
| `play-steam` | Play on the Steam page, and on the Library when it has no games |
| `library-source-<source>` | the Library's source filter chips (`all`, `steam`, `gog`, ...) |
| `game-tab-<about\|achievements\|launch\|saves\|install>` | the open tab of a game page |
| `stores-profile`, `stores-epic-launcher` | a store's profile strip, and the Epic Games Launcher row in Epic's |
| `setting-<key>` | every toggle and choice row, by its key (`setting-logs`, `setting-agent-commands`, ...) |
| `tab-<n>` | a page's tab strip (Setup's Overview, Controller, Session, Launcher...), by position |
| `drawer-tab-controller`, `-second_screen`, `-display`, `-effects`, `-vr`, `-games`, `-session` | the session drawer's pages |
+1 -1
View File
@@ -1,6 +1,6 @@
# Added games
Windows games the user adds to DroidDeck's Games tab, each a non-Steam shortcut in the
Windows games the user adds to DroidDeck's Library, each a non-Steam shortcut in the
Steam client that runs under `droiddeck-proton-arm64`. They show with the Custom chip.
## Adding
+1 -1
View File
@@ -227,7 +227,7 @@ flag for an experiment.
Device check (Thor): Alan Wake's American Nightmare with its 1280x720 Game Resolution runs on `:1`
at 1280x720 with no `--force-windows-fullscreen` in the session, 2 frame-size changes, no crash
in logcat. The Games tab and the drawer render without the switch.
in logcat. The Library and the drawer render without the switch.
## Steam picture position
+19 -5
View File
@@ -3,6 +3,21 @@
How the Stores section (`app/src/main/java/com/droiddeck/launcher/stores/`, `ui/Stores*.kt`) fits
the rest of the app, and the on-disk contract that makes a store game an ordinary added game.
## Library and Stores
The rail's Library (`ui/LibraryPage.kt`, rail key `games`) lists every owned game: installed ones
from the scanned library (`FrontEndState.steamGames`: Steam, added folders, store installs), then,
under Not installed, each signed-in store's `StoresState.library` products that are not. An
installed game claims its product by `"<source>:<storeId>" == CatalogItem.key`, so nothing shows
twice. Stores is only for getting games: a store opens on its storefront (search, then the shelves)
under a profile strip whose "Owned games in Library" opens the Library filtered to that store. The
Epic Games Launcher (open, settings, uninstall, install) lives in Epic's profile strip.
Every game opens one page (`ui/GameDetail.kt`) from either place: the hero and its actions by state
(installed, downloading, failed, unfinished, owned, not owned), then tabs LB/RB cycle through:
About, Achievements, Launch, Saves & cloud and Install, each hidden when it has nothing to show.
Library pages are `app:<appId>` for an installed game and `app:<store>:<id>` for a store product.
## Where a store game lives
```
@@ -180,8 +195,8 @@ The appid Steam derives (CRC32 of the quoted exe plus the name, high bit set) is
listing carries, so the live add and the writer never disagree. Uninstall removes the folder and the
listing drops the entry; the live client is asked to remove it too.
`Library.SteamGame.source` is `steam`, `gog`, `epic`, `amazon` or `added`; the Games tab shows it as
a chip on every row and in the hero.
`Library.SteamGame.source` is `steam`, `gog`, `epic`, `amazon` or `added`; the Library shows it as
a chip on every tile and in the hero, and filters by it.
## Launch
@@ -194,7 +209,7 @@ game that needs arguments or environment gets `.droiddeck-launch.bat` (`StoreLau
the variable it read the code into is cleared on that same line, so nothing of it is left in the
game's environment. A launcher from an earlier build is rewritten before the next Epic launch.
The code is minted for every launch, wherever it starts. The Games tab and Stores call
The code is minted for every launch, wherever it starts. The game page calls
`StoreLaunch.prepare` before they launch; the compat tool (`droiddeck-proton`,
`droiddeck-proton-wrap`) runs `droiddeck-store-launch` before Proton on every real launch, the Steam
client's Play button included. It finds the store launcher among Steam's arguments, reads the
@@ -202,7 +217,7 @@ sidecar beside it, and for an Epic game asks the app through `<session>/stores/r
(`StoreLaunchRequests` → `StoreLaunch.epicCode`), waiting up to 5 s; the app writes the code into
the game's folder itself. Each attempt logs `epic launch id=… code=yes|no reason=…`, never the code.
An Epic game's launch choices are the Epic card in the Games tab's launch settings - "Epic sign-in
An Epic game's launch choices are the Epic card in its page's Launch tab - "Epic sign-in
(EOS)" (on) and "Launch offline" (off), stored in the sidecar's `epic` block so both launch paths
read the same thing - and "Resolve Epic sign-in", which opens Epic's account page behind its
sign-in in Android's browser.
@@ -293,7 +308,6 @@ report "the store engine is not available".
| Setting | Pref | Where |
|---|---|---|
| Show Stores in the rail | `SessionPrefs.gameStoresEnabled` (off) | Setup › Stores |
| Open a store on: Library · Store | `SessionPrefs.storesOpenTab` (library) | the chip row's cog, Setup › Stores |
| Download speed tier | `SessionPrefs.gameStoresSpeedTier` (fast) | the cog, Setup › Stores, Downloads |
| Downloads at a time | `SessionPrefs.gameStoresParallel` (1) | Downloads |
| Install to (last pick, dialog default only) | `SessionPrefs.storesInstallTarget` | the Install dialog |
+1 -1
View File
@@ -3,7 +3,7 @@
Valve's x86-64 Linux Steam client, run unchanged under FEX beside the default arm64 client. It is
opt-in: Steam settings, **Steam client: x86-64** (`steamClientArch`, passed to the
session as `BL_STEAM_CLIENT_ARCH=x86_64`, applies from the next session), or the desktop menu entry
**Steam x86-64 (FEX, experimental)**. The Games tab lists the active client's games and tags its
**Steam x86-64 (FEX, experimental)**. The Library lists the active client's games and tags its
header "x86-64" while that client's list is shown. This page records why each workaround exists and what was seen
on devices. The timeline is in `docs/progress-log.md`.