mirror of
https://github.com/Droid-Deck/DroidDeck.git
synced 2026-10-14 19:27:09 -04:00
ui: move content, games, GPU drivers and loading copy to string resources
This commit is contained in:
1 parent
b4a86cb5bb
commit
3ba0d287e9
5 files changed
+217
-103
No files matched your search
@@ -1,5 +1,6 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import android.os.Build
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
@@ -185,8 +186,8 @@ private fun Content(
|
||||
Column(modifier = modifier.verticalScroll(rememberScrollState()).padding(horizontal = padH, vertical = padV)) {
|
||||
when {
|
||||
selected == "android-apps" && s.isHomeApp -> {
|
||||
Rise(0) { PageHeader("Android apps") { Chip("${s.androidApps.size} apps", ok = false) } }
|
||||
if (s.androidApps.isEmpty()) Rise(3) { Note("No launchable Android apps found.") }
|
||||
Rise(0) { PageHeader(stringResource(R.string.content_android_apps)) { Chip(stringResource(R.string.content_apps_count, s.androidApps.size), ok = false) } }
|
||||
if (s.androidApps.isEmpty()) Rise(3) { Note(stringResource(R.string.content_no_android_apps)) }
|
||||
else Rise(3, Modifier.fillMaxWidth()) {
|
||||
ArtGrid(s.androidApps.map { app ->
|
||||
Tile(
|
||||
@@ -208,8 +209,8 @@ private fun Content(
|
||||
val apps = UserAppsState.items
|
||||
var adding by rememberSaveable { mutableStateOf(false) }
|
||||
Rise(0) {
|
||||
PageHeader("Desktop") {
|
||||
if (s.desktopInstalled) Chip("● Desktop installed", ok = true) else Chip("Installs on first open", ok = false)
|
||||
PageHeader(stringResource(R.string.drawer_desktop)) {
|
||||
if (s.desktopInstalled) Chip(stringResource(R.string.content_desktop_installed), ok = true) else Chip(stringResource(R.string.content_desktop_first_open), ok = false)
|
||||
}
|
||||
}
|
||||
Rise(2) { DesktopCard(s, a) }
|
||||
@@ -224,7 +225,7 @@ private fun Content(
|
||||
}
|
||||
if (UserAppsState.working != null || UserAppsState.lastError != null) Rise(5) { UserAppsProgress() }
|
||||
if (!addAfterReady) {
|
||||
Rise(5) { SectionTitle("Available to install", available.size.toString()) }
|
||||
Rise(5) { SectionTitle(stringResource(R.string.content_available), available.size.toString()) }
|
||||
Rise(6) {
|
||||
LauncherGrid(available.map { GridItem.Emu(it) } + GridItem.Add, first = ready.isEmpty(), onSelect = onSelect, onAdd = { adding = true })
|
||||
}
|
||||
@@ -237,11 +238,11 @@ private fun Content(
|
||||
}
|
||||
selected.startsWith("emu:") -> {
|
||||
val e = s.emulators.firstOrNull { "emu:${it.id}" == selected }
|
||||
if (e == null) Note("Not installed.") else {
|
||||
if (e == null) Note(stringResource(R.string.content_not_installed)) else {
|
||||
val pkgId = Library.packageId(e.id)
|
||||
val pkg = pkgId?.let { id -> s.packages?.firstOrNull { it.id == id } }
|
||||
Rise(0) {
|
||||
BackLink("Desktop") { onSelect("desktop") }
|
||||
BackLink(stringResource(R.string.drawer_desktop)) { onSelect("desktop") }
|
||||
}
|
||||
Rise(1) {
|
||||
Row(
|
||||
@@ -252,7 +253,7 @@ private fun Content(
|
||||
Column {
|
||||
Text(e.name, fontSize = if (narrow) 22.sp else 26.sp, fontWeight = FontWeight.Bold, color = colors.onBackground)
|
||||
Text(
|
||||
e.system.replaceFirstChar { it.uppercase() } + if (e.installed) "" else " · not installed",
|
||||
e.system.replaceFirstChar { it.uppercase() }.let { if (e.installed) it else stringResource(R.string.content_not_installed_suffix, it) },
|
||||
fontSize = 14.sp, color = colors.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
@@ -261,21 +262,21 @@ private fun Content(
|
||||
if (e.installed) {
|
||||
Rise(3) {
|
||||
Actions {
|
||||
PrimaryButton("Open ${e.name}", enabled = !s.busy, main = true) { a.onEmulator(e) }
|
||||
PrimaryButton(stringResource(R.string.content_open_named, e.name), enabled = !s.busy, main = true) { a.onEmulator(e) }
|
||||
BusyChip(s)
|
||||
SecondaryButton("ROMs folder", onClick = a.onRoms)
|
||||
SecondaryButton(stringResource(R.string.setup_tool_roms), onClick = a.onRoms)
|
||||
if (pkg != null) SecondaryButton(
|
||||
if (pkg.kind == "appimage") "Remove" else "Hide",
|
||||
if (pkg.kind == "appimage") stringResource(R.string.store_remove) else stringResource(R.string.common_hide),
|
||||
enabled = s.packageBusyId == null && !s.sessionRunning,
|
||||
) { a.onRemovePackage(pkg.id) }
|
||||
}
|
||||
}
|
||||
Rise(4) { SectionTitle("Games", e.games.size.toString()) }
|
||||
Rise(4) { SectionTitle(stringResource(R.string.content_games), e.games.size.toString()) }
|
||||
if (e.games.isEmpty()) Rise(5) {
|
||||
Note(
|
||||
if (s.romsDir == null) "Choose a ROMs folder."
|
||||
else if (e.id == "retroarch") "Browse to /root/ROMs in RetroArch."
|
||||
else "Add ${e.system} games to ROMs/${e.system.substringBefore(' ')}.",
|
||||
if (s.romsDir == null) stringResource(R.string.content_choose_roms)
|
||||
else if (e.id == "retroarch") stringResource(R.string.content_retroarch_roms)
|
||||
else stringResource(R.string.content_add_roms, e.system, e.system.substringBefore(' ')),
|
||||
)
|
||||
}
|
||||
else Rise(5, Modifier.fillMaxWidth()) {
|
||||
@@ -285,46 +286,46 @@ private fun Content(
|
||||
if (pkg != null) Rise(2) {
|
||||
Actions {
|
||||
PrimaryButton(
|
||||
if (s.packageBusyId == pkg.id) "Installing…" else "Install ${e.name}",
|
||||
if (s.packageBusyId == pkg.id) stringResource(R.string.store_installing) else stringResource(R.string.content_install_named, e.name),
|
||||
enabled = s.packageBusyId == null && s.ready && !s.packageCatalogLoading && !s.sessionRunning,
|
||||
) { a.onInstallPackage(pkg.id) }
|
||||
if (s.sessionRunning) ActionChip("Stop session to install", ok = false)
|
||||
else if (!s.ready) ActionChip("Runtime required", ok = false)
|
||||
if (s.sessionRunning) ActionChip(stringResource(R.string.content_stop_to_install), ok = false)
|
||||
else if (!s.ready) ActionChip(stringResource(R.string.content_runtime_required), ok = false)
|
||||
}
|
||||
}
|
||||
Rise(3) {
|
||||
Box(Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 8.dp)) {
|
||||
Note(when {
|
||||
s.packageCatalogLoading -> "Loading install details…"
|
||||
pkg == null -> "Install details are unavailable right now. Try again when the package catalog is reachable."
|
||||
!s.ready -> "Install the Linux runtime from Setup before installing desktop apps."
|
||||
s.sessionRunning -> "Stop the active session before installing desktop apps."
|
||||
s.packageCatalogLoading -> stringResource(R.string.content_loading_details)
|
||||
pkg == null -> stringResource(R.string.content_details_unavailable)
|
||||
!s.ready -> stringResource(R.string.content_needs_runtime)
|
||||
s.sessionRunning -> stringResource(R.string.content_stop_first)
|
||||
pkg.notes.isNotBlank() -> pkg.notes
|
||||
else -> "Install ${e.name} into the Linux desktop runtime."
|
||||
else -> stringResource(R.string.content_install_into, e.name)
|
||||
})
|
||||
}
|
||||
}
|
||||
if (s.packageBusyId == pkg?.id) Rise(4) {
|
||||
val stage = s.packageStage
|
||||
Text(
|
||||
if (stage != null && s.packagePercent >= 0) "$stage · ${s.packagePercent}%" else stage ?: "Starting…",
|
||||
if (stage != null && s.packagePercent >= 0) stringResource(R.string.store_busy_percent_short, stage, s.packagePercent) else stage ?: stringResource(R.string.store_starting),
|
||||
fontSize = 12.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(bottom = 6.dp),
|
||||
)
|
||||
if (s.packagePercent >= 0) LinearProgressIndicator(progress = { s.packagePercent / 100f }, modifier = Modifier.fillMaxWidth().height(4.dp))
|
||||
else LinearProgressIndicator(modifier = Modifier.fillMaxWidth().height(4.dp))
|
||||
}
|
||||
if (pkg?.kind == "tar") Rise(5) { Note("Hiding this package takes it off Desktop; its files stay in the Linux runtime.") }
|
||||
if (pkg?.kind == "tar") Rise(5) { Note(stringResource(R.string.content_hide_note)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
selected.startsWith("rom:") -> {
|
||||
val pair = romFor(s, selected)
|
||||
if (pair == null) Note("That game is gone from the ROMs folder.") else {
|
||||
if (pair == null) Note(stringResource(R.string.content_rom_gone)) else {
|
||||
val (e, g) = pair
|
||||
Rise(0) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
BackLink(e.name) { onSelect("emu:${e.id}") }
|
||||
Eyebrow("Desktop · ${e.system}")
|
||||
Eyebrow(stringResource(R.string.content_desktop_system, e.system))
|
||||
}
|
||||
}
|
||||
Rise(1) { Title(g.name) }
|
||||
@@ -335,7 +336,7 @@ private fun Content(
|
||||
Spacer(Modifier.height(14.dp))
|
||||
Actions {
|
||||
Image(painterResource(e.iconRes), null, modifier = Modifier.size(40.dp))
|
||||
PrimaryButton("Launch in ${e.name}", enabled = !s.busy, main = true) { a.onRom(g) }
|
||||
PrimaryButton(stringResource(R.string.content_launch_in, e.name), enabled = !s.busy, main = true) { a.onRom(g) }
|
||||
if (s.busy) BusyChip(s) else ActionChip(g.hostPath.extension.uppercase().ifEmpty { "folder" }, ok = false)
|
||||
}
|
||||
}
|
||||
@@ -344,7 +345,7 @@ private fun Content(
|
||||
}
|
||||
val others = e.games.filter { it !== g }
|
||||
if (others.isNotEmpty()) {
|
||||
Rise(3) { SectionTitle("Also in ${e.name}", null) }
|
||||
Rise(3) { SectionTitle(stringResource(R.string.content_also_in, e.name), null) }
|
||||
Rise(4, Modifier.fillMaxWidth()) {
|
||||
ArtGrid(others.map { x ->
|
||||
val index = e.games.indexOf(x)
|
||||
@@ -354,7 +355,7 @@ private fun Content(
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> Note("That page is gone. Pick a section on the left.")
|
||||
else -> Note(stringResource(R.string.content_page_gone))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -368,7 +369,7 @@ private fun DesktopCard(s: FrontEndState, a: FrontEndActions) {
|
||||
val actions: @Composable () -> Unit = {
|
||||
Actions {
|
||||
// Enabled without a runtime or the desktop: the session's loading screen installs them first.
|
||||
PrimaryButton(if (s.desktopInstalled) "Open desktop" else "Install & open desktop", enabled = !s.busy, main = true, onClick = a.onDesktop)
|
||||
PrimaryButton(if (s.desktopInstalled) stringResource(R.string.content_open_desktop) else stringResource(R.string.content_install_open_desktop), enabled = !s.busy, main = true, onClick = a.onDesktop)
|
||||
Cog(onClick = a.onDesktopSettings)
|
||||
BusyChip(s)
|
||||
}
|
||||
@@ -383,8 +384,8 @@ private fun DesktopCard(s: FrontEndState, a: FrontEndActions) {
|
||||
modifier = Modifier.size(52.dp).clip(Shape14).background(colors.surfaceVariant).border(1.dp, pal.line2, Shape14),
|
||||
) { Icon(Icons.Outlined.DesktopWindows, contentDescription = null, tint = colors.onBackground, modifier = Modifier.size(26.dp)) }
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text("Linux desktop", fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
Text("LXQt, Firefox and your emulators", fontSize = 14.sp, color = colors.onSurfaceVariant, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
Text(stringResource(R.string.content_linux_desktop), fontSize = 18.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
Text(stringResource(R.string.content_linux_desktop_hint), fontSize = 14.sp, color = colors.onSurfaceVariant, maxLines = 2, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
if (!narrow) actions()
|
||||
}
|
||||
@@ -599,7 +600,7 @@ private fun EmulatorTile(e: Library.Emulator, modifier: Modifier, isFirst: Boole
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val system = e.system.replaceFirstChar { it.uppercase() }
|
||||
val detail = if (e.installed && e.id != "retroarch") "$system · ${e.games.size} game${if (e.games.size == 1) "" else "s"}" else system
|
||||
val detail = if (e.installed && e.id != "retroarch") "$system · " + pluralStringResource(R.plurals.mode_added_count, e.games.size, e.games.size) else system
|
||||
TileFrame("emu:${e.id}", modifier, isFirst, filled = e.installed, onClick = onClick) {
|
||||
Image(painterResource(e.iconRes), contentDescription = null, modifier = Modifier.size(if (e.installed) 44.dp else 36.dp))
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
@@ -607,7 +608,7 @@ private fun EmulatorTile(e: Library.Emulator, modifier: Modifier, isFirst: Boole
|
||||
Text(detail, fontSize = if (e.installed) 13.sp else 12.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
}
|
||||
if (!e.installed) Text(
|
||||
"Install", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = pal.signal,
|
||||
stringResource(R.string.setup_install), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = pal.signal,
|
||||
modifier = Modifier.clip(RoundedCornerShape(8.dp)).border(1.dp, pal.line2, RoundedCornerShape(8.dp)).padding(horizontal = 10.dp, vertical = 6.dp),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
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
|
||||
@@ -66,10 +68,10 @@ internal fun GamesPage(s: FrontEndState, a: FrontEndActions, selected: String, o
|
||||
val current = games.firstOrNull { "app:${it.appId}" == selected } ?: games.firstOrNull()
|
||||
if (current == null) {
|
||||
Column(modifier = modifier.padding(horizontal = if (narrow) 16.dp else 22.dp, vertical = if (narrow) 12.dp else 18.dp)) {
|
||||
Rise(0) { PageHeader("Games") }
|
||||
Rise(1) { Note("Games you install in Steam show up here. Open Steam, install one, and it appears after the session.") }
|
||||
Rise(0) { PageHeader(stringResource(R.string.content_games)) }
|
||||
Rise(1) { Note(stringResource(R.string.games_empty)) }
|
||||
Rise(2) {
|
||||
Actions { PrimaryButton("Play Steam", enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow, modifier = Modifier.padding(top = 12.dp), onClick = a.onPlay) }
|
||||
Actions { PrimaryButton(stringResource(R.string.games_play_steam), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow, modifier = Modifier.padding(top = 12.dp), onClick = a.onPlay) }
|
||||
}
|
||||
}
|
||||
return
|
||||
@@ -86,7 +88,7 @@ internal fun GamesPage(s: FrontEndState, a: FrontEndActions, selected: String, o
|
||||
if (!narrow) Poster(current.art, current.name, Modifier.width(168.dp))
|
||||
}
|
||||
}
|
||||
Rise(1) { SectionTitle("Launch settings", null) }
|
||||
Rise(1) { SectionTitle(stringResource(R.string.games_launch_settings), null) }
|
||||
Rise(2) { LaunchSettings(s, a, host) }
|
||||
}
|
||||
return
|
||||
@@ -104,7 +106,7 @@ internal fun GamesPage(s: FrontEndState, a: FrontEndActions, selected: String, o
|
||||
GameHeroCopy(current, if (narrow) 24.sp else 32.sp)
|
||||
GameActions(current, s, a)
|
||||
}
|
||||
SectionTitle("Launch settings", null)
|
||||
SectionTitle(stringResource(R.string.games_launch_settings), null)
|
||||
LaunchSettings(s, a, host)
|
||||
}
|
||||
}
|
||||
@@ -114,12 +116,12 @@ internal fun GamesPage(s: FrontEndState, a: FrontEndActions, selected: String, o
|
||||
@Composable
|
||||
private fun GameActions(g: Library.SteamGame, s: FrontEndState, a: FrontEndActions) {
|
||||
Actions {
|
||||
PrimaryButton("Launch", enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow) { a.onSteamGame(g) }
|
||||
PrimaryButton(stringResource(R.string.games_launch), enabled = !s.busy, main = true, icon = Icons.Filled.PlayArrow) { a.onSteamGame(g) }
|
||||
g.gameFiles?.takeIf { it.isDirectory }?.let { dir ->
|
||||
SecondaryButton("Game files", compact = true) { a.onBrowseFiles(dir) }
|
||||
SecondaryButton(stringResource(R.string.games_files), compact = true) { a.onBrowseFiles(dir) }
|
||||
}
|
||||
g.protonPrefix?.takeIf { it.isDirectory }?.let { dir ->
|
||||
SecondaryButton("Proton prefix", compact = true) { a.onBrowseFiles(dir) }
|
||||
SecondaryButton(stringResource(R.string.games_prefix), compact = true) { a.onBrowseFiles(dir) }
|
||||
// Only games added to the library; Steam titles keep their saves with Steam Cloud.
|
||||
if (g.library == Library.ADDED) ManageSaves(g, dir, a)
|
||||
}
|
||||
@@ -141,28 +143,32 @@ private fun ManageSaves(g: Library.SteamGame, prefix: java.io.File, a: FrontEndA
|
||||
}
|
||||
val found = saves
|
||||
val summary = when {
|
||||
found == null -> "Looking for this game's saves…"
|
||||
found.isEmpty() -> "No save folder found by name: an export takes the whole user folder."
|
||||
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
|
||||
(if (found.size == 1) found[0].relPath else "${found.size} save folders") + " · ${found.sumOf { it.files }} files · " +
|
||||
(if (mb < 1) "${(mb * 1024).toInt()} KB" else String.format("%.1f MB", mb))
|
||||
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 {
|
||||
SecondaryButton("Manage saves", compact = true) { open = !open }
|
||||
AnchoredMenu(open, onDismiss = { open = false }, title = "Game saves", note = summary) { first ->
|
||||
MenuItem("Import saves…", checked = false, detail = "A GameHub or Winlator zip · the saves there now are backed up first", focusRequester = first) {
|
||||
SecondaryButton(stringResource(R.string.games_manage_saves), compact = true) { open = !open }
|
||||
AnchoredMenu(open, onDismiss = { open = false }, title = stringResource(R.string.games_saves), note = summary) { first ->
|
||||
MenuItem(stringResource(R.string.games_import_saves), checked = false, detail = stringResource(R.string.games_import_saves_hint), focusRequester = first) {
|
||||
open = false; a.onSaveImport(g)
|
||||
}
|
||||
MenuItem("Export as GameHub zip…", checked = false, detail = "Saves under steamuser, for GameHub and BannerHub") {
|
||||
MenuItem(stringResource(R.string.games_export_gamehub), checked = false, detail = stringResource(R.string.games_export_gamehub_hint)) {
|
||||
open = false; a.onSaveExport(g, com.droiddeck.launcher.session.GameSaves.Layout.GAMEHUB)
|
||||
}
|
||||
MenuItem("Export as Winlator zip…", checked = false, detail = "Saves under xuser, for Winlator, WinNative and Bannerlator") {
|
||||
MenuItem(stringResource(R.string.games_export_winlator), checked = false, detail = stringResource(R.string.games_export_winlator_hint)) {
|
||||
open = false; a.onSaveExport(g, com.droiddeck.launcher.session.GameSaves.Layout.WINLATOR)
|
||||
}
|
||||
found?.firstOrNull()?.let { d ->
|
||||
MenuItem("Open save folder", checked = false, detail = d.relPath) {
|
||||
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))
|
||||
}
|
||||
}
|
||||
@@ -197,7 +203,7 @@ private fun GameList(
|
||||
modifier = modifier.verticalScroll(rememberScrollState()).padding(start = 12.dp, end = 10.dp, top = 16.dp, bottom = 16.dp),
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.Bottom, horizontalArrangement = Arrangement.spacedBy(8.dp), modifier = Modifier.padding(start = 6.dp, bottom = 10.dp)) {
|
||||
Text("Games", fontSize = 22.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1)
|
||||
Text(stringResource(R.string.content_games), fontSize = 22.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 1)
|
||||
Text(games.size.toString(), fontSize = 13.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(bottom = 3.dp))
|
||||
}
|
||||
for (g in games) key(g.appId) {
|
||||
@@ -231,7 +237,7 @@ private fun GameRow(g: Library.SteamGame, selected: Boolean, onSelect: () -> Uni
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(g.name, fontSize = 14.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis)
|
||||
Text(
|
||||
lastPlayedText(g.lastPlayed)?.removePrefix("Last played ")?.replaceFirstChar { it.uppercase() } ?: "Never played",
|
||||
playedSpan(g.lastPlayed)?.replaceFirstChar { it.uppercase() } ?: stringResource(R.string.games_never_played),
|
||||
fontSize = 12.sp, color = colors.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
@@ -256,30 +262,35 @@ internal fun PageHeader(title: String, trailing: @Composable RowScope.() -> Unit
|
||||
/** Why a launch button is greyed out: the runtime is being worked on. Nothing when it is not. */
|
||||
@Composable
|
||||
internal fun BusyChip(s: FrontEndState) {
|
||||
if (s.busy) ActionChip(if (s.percent >= 0) "Runtime busy · ${s.percent}%" else "Runtime busy", ok = false)
|
||||
if (s.busy) ActionChip(if (s.percent >= 0) stringResource(R.string.games_runtime_busy_percent, s.percent) else stringResource(R.string.games_runtime_busy), ok = false)
|
||||
}
|
||||
|
||||
/** Whether Steam can start, said where Play is rather than only in Setup. */
|
||||
@Composable
|
||||
internal fun RuntimeChip(s: FrontEndState) = when {
|
||||
s.busy -> Chip(if (s.percent >= 0) "${s.stage} · ${s.percent}%" else s.stage, ok = false)
|
||||
!s.ready -> Chip("Runtime installs on first Play", ok = false)
|
||||
s.available != null && s.available != s.installed -> Chip("Runtime update available", ok = false)
|
||||
else -> Chip("● Runtime ready", ok = true)
|
||||
s.busy -> Chip(if (s.percent >= 0) stringResource(R.string.setup_runtime_progress, s.stage, s.percent) else s.stage, ok = false)
|
||||
!s.ready -> Chip(stringResource(R.string.games_runtime_first_play), ok = false)
|
||||
s.available != null && s.available != s.installed -> Chip(stringResource(R.string.games_runtime_update), ok = false)
|
||||
else -> Chip(stringResource(R.string.games_runtime_ready), ok = true)
|
||||
}
|
||||
|
||||
/** "Last played 3 days ago" from Steam's unix seconds; null for a game never played. */
|
||||
private fun lastPlayedText(lastPlayed: Long): String? {
|
||||
@Composable
|
||||
private fun lastPlayedText(lastPlayed: Long): String? =
|
||||
playedSpan(lastPlayed)?.let { stringResource(R.string.games_last_played, it.replaceFirstChar { c -> c.lowercase() }) }
|
||||
|
||||
/** "3 days ago" from Steam's unix seconds; null for a game never played. */
|
||||
private fun playedSpan(lastPlayed: Long): String? {
|
||||
if (lastPlayed <= 0L) return null
|
||||
val span = android.text.format.DateUtils.getRelativeTimeSpanString(
|
||||
return android.text.format.DateUtils.getRelativeTimeSpanString(
|
||||
lastPlayed * 1000L, System.currentTimeMillis(), android.text.format.DateUtils.MINUTE_IN_MILLIS,
|
||||
).toString()
|
||||
return "Last played " + span.replaceFirstChar { it.lowercase() }
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun libraryLabel(library: String): String = when (library) {
|
||||
"internal" -> "Internal storage"
|
||||
Library.ADDED -> "Added game"
|
||||
"internal" -> stringResource(R.string.games_internal)
|
||||
Library.ADDED -> stringResource(R.string.games_added)
|
||||
else -> library
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import com.droiddeck.launcher.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -120,29 +122,28 @@ internal fun GpuDriversPanel(s: GpuDriversState, a: GpuDriversActions, onAdvance
|
||||
DeviceCard(s)
|
||||
if (!s.unsupported) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp)) {
|
||||
ModeCard(Icons.Outlined.AutoAwesome, "Auto", "The best pair for this GPU, kept up to date", s.auto, Modifier.weight(1f)) { a.onAuto(true) }
|
||||
ModeCard(Icons.Outlined.Tune, "Manual", "Pick a pair yourself", !s.auto, Modifier.weight(1f)) { a.onAuto(false) }
|
||||
ModeCard(Icons.Outlined.AutoAwesome, stringResource(R.string.common_auto), stringResource(R.string.gpu_auto_hint), s.auto, Modifier.weight(1f)) { a.onAuto(true) }
|
||||
ModeCard(Icons.Outlined.Tune, stringResource(R.string.mode_suspend_manual), stringResource(R.string.gpu_manual_hint), !s.auto, Modifier.weight(1f)) { a.onAuto(false) }
|
||||
}
|
||||
if (s.auto) AutoCard(s)
|
||||
else {
|
||||
val shown = s.pairs.filter { it.suits || showAll }
|
||||
if (shown.isEmpty()) Text(
|
||||
if (s.checking) "Checking for drivers…" else "No drivers listed yet. Tap refresh to look for them.",
|
||||
if (s.checking) stringResource(R.string.gpu_checking) else stringResource(R.string.gpu_none_listed),
|
||||
fontSize = 13.sp, color = colors.onSurfaceVariant, modifier = Modifier.padding(vertical = 8.dp),
|
||||
)
|
||||
for (p in shown) PairLine(p, s.busy == p.key, s.percent, enabled = s.busy == null) { a.onPair(p.key) }
|
||||
if (s.pairs.any { !it.suits }) FocusText(
|
||||
if (showAll) "Hide drivers for other GPUs" else "Show drivers for other GPUs", pal.signal,
|
||||
if (showAll) stringResource(R.string.gpu_hide_other) else stringResource(R.string.gpu_show_other), pal.signal,
|
||||
) { showAll = !showAll }
|
||||
}
|
||||
}
|
||||
SettingsGroup("Advanced") {
|
||||
val together = s.activeBundle?.let { "Set together with the %s driver: $it carries both." }
|
||||
SettingsRow("Runtime driver", together?.format("display") ?: "Games and the desktop draw with it.") {
|
||||
DriverDropdown(s.linuxRows, s.linuxSelected, "Runtime default", a.onSelectLinux) { onAdvanced("rt") }
|
||||
SettingsGroup(stringResource(R.string.gpu_advanced)) {
|
||||
SettingsRow(stringResource(R.string.comp_runtime_driver), s.activeBundle?.let { stringResource(R.string.gpu_set_with_display, it) } ?: stringResource(R.string.gpu_runtime_hint)) {
|
||||
DriverDropdown(s.linuxRows, s.linuxSelected, stringResource(R.string.gpu_runtime_default), a.onSelectLinux) { onAdvanced("rt") }
|
||||
}
|
||||
SettingsRow("Display driver", together?.format("runtime") ?: "Puts frames on the screen. Changes apply after DroidDeck restarts.") {
|
||||
DriverDropdown(s.androidRows, s.androidSelected, "Auto - picked by GPU", a.onSelectAndroid) { onAdvanced("panel") }
|
||||
SettingsRow(stringResource(R.string.comp_display_driver), s.activeBundle?.let { stringResource(R.string.gpu_set_with_runtime, it) } ?: stringResource(R.string.gpu_display_hint)) {
|
||||
DriverDropdown(s.androidRows, s.androidSelected, stringResource(R.string.gpu_auto_by_gpu), a.onSelectAndroid) { onAdvanced("panel") }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +175,7 @@ private fun DriverDropdown(rows: List<DriverRow>, selected: String, fallback: St
|
||||
onClick = { open = false; if (row.id != selected) onSelect(row.id) },
|
||||
)
|
||||
HorizontalDivider(color = pal.line)
|
||||
DropdownMenuItem(text = { Text("Download, import or delete…", fontSize = 14.sp, color = pal.signal) }, onClick = { open = false; onManage() })
|
||||
DropdownMenuItem(text = { Text(stringResource(R.string.gpu_manage), fontSize = 14.sp, color = pal.signal) }, onClick = { open = false; onManage() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,14 +218,14 @@ private fun AutoCard(s: GpuDriversState) {
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.fillMaxWidth().clip(Shape14).background(colors.surface).border(1.dp, pal.line, Shape14).padding(14.dp),
|
||||
) {
|
||||
Text("IN USE", fontSize = 11.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = colors.onSurfaceVariant)
|
||||
Text(stringResource(R.string.gpu_in_use_caps), fontSize = 11.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = colors.onSurfaceVariant)
|
||||
Text(
|
||||
active?.let { "${it.name} ${it.version}" } ?: s.activeBundle ?: "The drivers built into DroidDeck",
|
||||
active?.let { "${it.name} ${it.version}" } ?: s.activeBundle ?: stringResource(R.string.gpu_builtin),
|
||||
fontSize = 16.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground,
|
||||
)
|
||||
Text(
|
||||
if (active != null || s.activeBundle != null) "Runtime and display drivers set together as a matched pair."
|
||||
else recommended?.let { "Recommended for this GPU: ${it.name}." } ?: "Tap refresh to look for drivers for this GPU.",
|
||||
if (active != null || s.activeBundle != null) stringResource(R.string.gpu_matched_pair)
|
||||
else recommended?.let { stringResource(R.string.gpu_recommended_for, it.name) } ?: stringResource(R.string.gpu_refresh_hint),
|
||||
fontSize = 13.sp, color = colors.onSurfaceVariant,
|
||||
)
|
||||
if (s.busy != null) {
|
||||
@@ -284,7 +285,7 @@ private fun PairLine(p: PairRow, busy: Boolean, percent: Int, enabled: Boolean,
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp)) {
|
||||
Text(p.name, fontSize = 14.5.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground, maxLines = 1, overflow = TextOverflow.Ellipsis, modifier = Modifier.weight(1f, fill = false))
|
||||
if (p.recommended) Text(
|
||||
"RECOMMENDED", fontSize = 10.5.sp, fontWeight = FontWeight.Bold, letterSpacing = 1.sp, color = pal.good,
|
||||
stringResource(R.string.gpu_recommended_caps), fontSize = 10.5.sp, fontWeight = FontWeight.Bold, letterSpacing = 1.sp, color = pal.good,
|
||||
modifier = Modifier.clip(RoundedCornerShape(5.dp)).background(pal.good.copy(alpha = 0.12f)).padding(horizontal = 6.dp, vertical = 2.dp),
|
||||
)
|
||||
}
|
||||
@@ -295,10 +296,10 @@ private fun PairLine(p: PairRow, busy: Boolean, percent: Int, enabled: Boolean,
|
||||
Text(if (percent >= 0) "$percent%" else "…", fontSize = 12.sp, color = colors.onSurfaceVariant)
|
||||
LinearProgressIndicator(progress = { percent.coerceAtLeast(0) / 100f }, modifier = Modifier.fillMaxWidth().padding(top = 4.dp))
|
||||
}
|
||||
p.active -> Text("In use", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = pal.signal)
|
||||
!p.complete -> Text("Unavailable", fontSize = 13.sp, color = colors.onSurfaceVariant)
|
||||
p.installed -> Text("Use", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
else -> Text("Download", fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
p.active -> Text(stringResource(R.string.comp_tag_in_use), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = pal.signal)
|
||||
!p.complete -> Text(stringResource(R.string.gpu_unavailable), fontSize = 13.sp, color = colors.onSurfaceVariant)
|
||||
p.installed -> Text(stringResource(R.string.common_use), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
else -> Text(stringResource(R.string.common_download), fontSize = 13.sp, fontWeight = FontWeight.SemiBold, color = colors.onBackground)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package com.droiddeck.launcher.ui
|
||||
|
||||
import com.droiddeck.launcher.R
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.animation.core.animateFloat
|
||||
import androidx.compose.animation.core.infiniteRepeatable
|
||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
||||
@@ -89,17 +91,17 @@ fun HudText(text: String) {
|
||||
}
|
||||
|
||||
/** One line of the loading screen's checklist; [detail] is what it says while it is the one running. */
|
||||
private class LoadStage(val label: String, val detail: String)
|
||||
private class LoadStage(@androidx.annotation.StringRes val label: Int, @androidx.annotation.StringRes val detail: Int)
|
||||
|
||||
private fun loadStages(steam: Boolean): List<LoadStage> = if (steam) listOf(
|
||||
LoadStage("Linux runtime", "Getting the Linux runtime ready"),
|
||||
LoadStage("Session", "Starting the display and audio"),
|
||||
LoadStage("Steam client", "Checking the Steam client and its compatibility tools"),
|
||||
LoadStage("Opening Steam", "Waiting for Steam's first frame"),
|
||||
LoadStage(R.string.load_runtime, R.string.load_runtime_detail),
|
||||
LoadStage(R.string.load_session, R.string.load_session_detail),
|
||||
LoadStage(R.string.load_steam, R.string.load_steam_detail),
|
||||
LoadStage(R.string.load_opening_steam, R.string.load_opening_steam_detail),
|
||||
) else listOf(
|
||||
LoadStage("Linux runtime", "Getting the Linux runtime ready"),
|
||||
LoadStage("Desktop", "Getting the desktop ready"),
|
||||
LoadStage("Opening", "Waiting for the first frame"),
|
||||
LoadStage(R.string.load_runtime, R.string.load_runtime_detail),
|
||||
LoadStage(R.string.load_desktop, R.string.load_desktop_detail),
|
||||
LoadStage(R.string.load_opening, R.string.load_opening_detail),
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -128,7 +130,7 @@ private fun readableStep(step: String): Boolean {
|
||||
@Composable
|
||||
fun LoadingOverlay(
|
||||
step: String, percent: Int, elapsed: String, hint: String, ended: Boolean,
|
||||
title: String = "Starting Steam", steam: Boolean = true, onCancel: (() -> Unit)? = null,
|
||||
title: String = stringResource(R.string.load_starting_steam), steam: Boolean = true, onCancel: (() -> Unit)? = null,
|
||||
/** An ended session's exit status and log path, shown small under the advice. */
|
||||
endedDetail: String? = null,
|
||||
onRetry: (() -> Unit)? = null,
|
||||
@@ -147,7 +149,7 @@ fun LoadingOverlay(
|
||||
if (at > reached) reached = at
|
||||
}
|
||||
val active = reached.coerceAtMost(stages.lastIndex)
|
||||
val detail = if (percent >= 0 || readableStep(step)) step.replaceFirstChar { it.uppercase() } else stages[active].detail
|
||||
val detail = if (percent >= 0 || readableStep(step)) step.replaceFirstChar { it.uppercase() } else stringResource(stages[active].detail)
|
||||
val context = LocalContext.current
|
||||
// The Steam tab's wall behind it, dimmed and slowed: starting Steam reads as the same place settling in.
|
||||
val games by produceState(emptyList<Library.SteamGame>()) {
|
||||
@@ -186,7 +188,7 @@ fun LoadingOverlay(
|
||||
}
|
||||
Spacer(Modifier.height(30.dp))
|
||||
Text(
|
||||
if (ended) "The session ended" else title, fontSize = 20.sp, fontWeight = FontWeight.SemiBold,
|
||||
if (ended) stringResource(R.string.load_ended) else title, fontSize = 20.sp, fontWeight = FontWeight.SemiBold,
|
||||
color = colors.onBackground, textAlign = TextAlign.Center, maxLines = 2, overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
@@ -223,9 +225,9 @@ private fun EndedActions(onRetry: (() -> Unit)?, onShareLogs: (() -> Unit)?, onC
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier.padding(top = 22.dp)) {
|
||||
var firstUsed = false
|
||||
fun claim(): Modifier = if (firstUsed) Modifier else { firstUsed = true; Modifier.focusRequester(first) }
|
||||
if (onRetry != null) PrimaryButton("Try again", modifier = claim(), onClick = onRetry)
|
||||
if (onShareLogs != null) SecondaryButton("Share logs", modifier = claim(), onClick = onShareLogs)
|
||||
if (onClose != null) SecondaryButton("Back", modifier = claim(), onClick = onClose)
|
||||
if (onRetry != null) PrimaryButton(stringResource(R.string.store_try_again), modifier = claim(), onClick = onRetry)
|
||||
if (onShareLogs != null) SecondaryButton(stringResource(R.string.drawer_share_logs), modifier = claim(), onClick = onShareLogs)
|
||||
if (onClose != null) SecondaryButton(stringResource(R.string.mode_back), modifier = claim(), onClick = onClose)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,9 +239,10 @@ private fun LoadSegments(count: Int, active: Int, percent: Int, modifier: Modifi
|
||||
val run = rememberInfiniteTransition(label = "segment")
|
||||
.animateFloat(-0.4f, 1f, infiniteRepeatable(tween(1300, easing = FastOutSlowInEasing)), label = "run")
|
||||
val still = Motion.scale == 0f
|
||||
val stepDescription = stringResource(R.string.load_step, active + 1, count)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(6.dp),
|
||||
modifier = modifier.semantics { contentDescription = "Step ${active + 1} of $count" },
|
||||
modifier = modifier.semantics { contentDescription = stepDescription },
|
||||
) {
|
||||
repeat(count) { i ->
|
||||
Box(
|
||||
@@ -272,13 +275,13 @@ private fun CancelHint(onCancel: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Box(contentAlignment = Alignment.Center, modifier = Modifier.size(20.dp).clip(CircleShape).background(colors.onBackground)) {
|
||||
Text("B", fontSize = 12.sp, fontWeight = FontWeight.Bold, color = colors.background)
|
||||
}
|
||||
Text("Cancel", fontSize = 13.sp, color = colors.onSurfaceVariant)
|
||||
Text(stringResource(R.string.common_cancel), fontSize = 13.sp, color = colors.onSurfaceVariant)
|
||||
}
|
||||
}
|
||||
|
||||
/** A session paused in the background, ready to pick up where it left off. */
|
||||
@Composable
|
||||
fun SessionPausedOverlay(title: String = "Steam is paused", onResume: () -> Unit) {
|
||||
fun SessionPausedOverlay(title: String = stringResource(R.string.load_steam_paused), onResume: () -> Unit) {
|
||||
val colors = MaterialTheme.colorScheme
|
||||
val pal = LocalPalette.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
@@ -304,7 +307,7 @@ fun SessionPausedOverlay(title: String = "Steam is paused", onResume: () -> Unit
|
||||
}
|
||||
Text(title, fontSize = 28.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, textAlign = TextAlign.Center)
|
||||
PrimaryButton(
|
||||
"Resume",
|
||||
stringResource(R.string.resume_session),
|
||||
modifier = Modifier.padding(top = 14.dp).focusRequester(resumeFocus).controllerConfirm(onClick = onResume),
|
||||
onClick = onResume,
|
||||
)
|
||||
|
||||
@@ -740,4 +740,102 @@
|
||||
<string name="ctrl_onscreen_buttons">On-screen buttons</string>
|
||||
<string name="ctrl_defaults">Defaults</string>
|
||||
<string name="ctrl_reset_mapping">Reset mapping</string>
|
||||
<string name="content_android_apps">Android apps</string>
|
||||
<string name="content_apps_count">%1$d apps</string>
|
||||
<string name="content_no_android_apps">No launchable Android apps found.</string>
|
||||
<string name="content_desktop_installed">● Desktop installed</string>
|
||||
<string name="content_desktop_first_open">Installs on first open</string>
|
||||
<string name="content_available">Available to install</string>
|
||||
<string name="content_not_installed">Not installed.</string>
|
||||
<string name="content_not_installed_suffix">%1$s · not installed</string>
|
||||
<string name="content_open_named">Open %1$s</string>
|
||||
<string name="content_games">Games</string>
|
||||
<string name="content_choose_roms">Choose a ROMs folder.</string>
|
||||
<string name="content_retroarch_roms">Browse to /root/ROMs in RetroArch.</string>
|
||||
<string name="content_add_roms">Add %1$s games to ROMs/%2$s.</string>
|
||||
<string name="content_install_named">Install %1$s</string>
|
||||
<string name="content_stop_to_install">Stop session to install</string>
|
||||
<string name="content_runtime_required">Runtime required</string>
|
||||
<string name="content_loading_details">Loading install details…</string>
|
||||
<string name="content_details_unavailable">Install details are unavailable. Try again later.</string>
|
||||
<string name="content_needs_runtime">Install the Linux runtime from Setup first.</string>
|
||||
<string name="content_stop_first">Stop the running session to install desktop apps.</string>
|
||||
<string name="content_install_into">Installs %1$s into the Linux desktop.</string>
|
||||
<string name="store_busy_percent_short">%1$s · %2$d%%</string>
|
||||
<string name="content_hide_note">Hiding removes it from Desktop. Its files stay in the Linux runtime.</string>
|
||||
<string name="content_rom_gone">That game is no longer in the ROMs folder.</string>
|
||||
<string name="content_desktop_system">Desktop · %1$s</string>
|
||||
<string name="content_launch_in">Launch in %1$s</string>
|
||||
<string name="content_also_in">Also in %1$s</string>
|
||||
<string name="content_page_gone">That page is gone. Pick a section on the left.</string>
|
||||
<string name="content_open_desktop">Open desktop</string>
|
||||
<string name="content_install_open_desktop">Install & open desktop</string>
|
||||
<string name="content_linux_desktop">Linux desktop</string>
|
||||
<string name="content_linux_desktop_hint">LXQt, Firefox and your emulators</string>
|
||||
<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_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>
|
||||
<string name="games_saves_none">No save folder found by name. Export takes the whole user folder.</string>
|
||||
<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_import_saves_hint">GameHub or Winlator zip. Current saves are backed up first.</string>
|
||||
<string name="games_export_gamehub">Export as GameHub zip…</string>
|
||||
<string name="games_export_gamehub_hint">For GameHub and BannerHub</string>
|
||||
<string name="games_export_winlator">Export as Winlator zip…</string>
|
||||
<string name="games_export_winlator_hint">For Winlator, WinNative and Bannerlator</string>
|
||||
<string name="games_open_save_folder">Open save folder</string>
|
||||
<string name="games_never_played">Never played</string>
|
||||
<string name="games_runtime_busy_percent">Runtime busy · %1$d%%</string>
|
||||
<string name="games_runtime_busy">Runtime busy</string>
|
||||
<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="gpu_auto_hint">The best pair for this GPU, kept up to date</string>
|
||||
<string name="gpu_manual_hint">Pick a pair yourself</string>
|
||||
<string name="gpu_checking">Checking for drivers…</string>
|
||||
<string name="gpu_none_listed">No drivers listed yet. Refresh to look for them.</string>
|
||||
<string name="gpu_hide_other">Hide drivers for other GPUs</string>
|
||||
<string name="gpu_show_other">Show drivers for other GPUs</string>
|
||||
<string name="gpu_advanced">Advanced</string>
|
||||
<string name="gpu_set_with_display">Set together with the display driver: %1$s carries both.</string>
|
||||
<string name="gpu_set_with_runtime">Set together with the runtime driver: %1$s carries both.</string>
|
||||
<string name="gpu_runtime_hint">Used by games and the desktop.</string>
|
||||
<string name="gpu_runtime_default">Runtime default</string>
|
||||
<string name="gpu_display_hint">Draws frames to the screen. Applies after DroidDeck restarts.</string>
|
||||
<string name="gpu_auto_by_gpu">Auto - picked by GPU</string>
|
||||
<string name="gpu_manage">Download, import or delete…</string>
|
||||
<string name="gpu_in_use_caps">IN USE</string>
|
||||
<string name="gpu_builtin">The drivers built into DroidDeck</string>
|
||||
<string name="gpu_matched_pair">Runtime and display drivers set together as a matched pair.</string>
|
||||
<string name="gpu_recommended_for">Recommended for this GPU: %1$s.</string>
|
||||
<string name="gpu_refresh_hint">Refresh to look for drivers for this GPU.</string>
|
||||
<string name="gpu_recommended_caps">RECOMMENDED</string>
|
||||
<string name="gpu_unavailable">Unavailable</string>
|
||||
<string name="load_runtime">Linux runtime</string>
|
||||
<string name="load_runtime_detail">Getting the Linux runtime ready</string>
|
||||
<string name="load_session">Session</string>
|
||||
<string name="load_session_detail">Starting display and audio</string>
|
||||
<string name="load_steam">Steam client</string>
|
||||
<string name="load_steam_detail">Checking Steam and its compatibility tools</string>
|
||||
<string name="load_opening_steam">Opening Steam</string>
|
||||
<string name="load_opening_steam_detail">Waiting for Steam\'s first frame</string>
|
||||
<string name="load_desktop">Desktop</string>
|
||||
<string name="load_desktop_detail">Getting the desktop ready</string>
|
||||
<string name="load_opening">Opening</string>
|
||||
<string name="load_opening_detail">Waiting for the first frame</string>
|
||||
<string name="load_starting_steam">Starting Steam</string>
|
||||
<string name="load_ended">The session ended</string>
|
||||
<string name="load_step">Step %1$d of %2$d</string>
|
||||
<string name="load_steam_paused">Steam is paused</string>
|
||||
</resources>
|
||||
Reference in new issue
Block a user