diff --git a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndGames.kt b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndGames.kt index 60a325541..d11478455 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/FrontEndGames.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/FrontEndGames.kt @@ -221,7 +221,7 @@ private fun ManageSaves(g: Library.SteamGame, prefix: java.io.File, a: FrontEndA @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) + SourceChip(g.source, withName = true) Text( (lastPlayedText(g.lastPlayed) ?: libraryLabel(g)).uppercase(), fontSize = 12.sp, fontWeight = FontWeight.SemiBold, letterSpacing = 1.2.sp, color = LocalPalette.current.signal, diff --git a/app/src/main/java/com/droiddeck/launcher/ui/StoreChips.kt b/app/src/main/java/com/droiddeck/launcher/ui/StoreChips.kt index c35a9beda..399bb2bde 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/StoreChips.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/StoreChips.kt @@ -1,6 +1,21 @@ package com.droiddeck.launcher.ui import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.widthIn +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.CreateNewFolder +import androidx.compose.material3.Icon +import androidx.compose.ui.Alignment +import androidx.compose.ui.graphics.painter.Painter +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme @@ -17,7 +32,7 @@ import com.droiddeck.launcher.R import com.droiddeck.launcher.frontend.Library import com.droiddeck.launcher.stores.Store -// Where a game came from, as a small coloured chip: Steam, one of the stores, or an added folder. +// Where a game came from, as a small coloured chip with its mark: Steam, one of the stores, or an added folder. /** The chip's colours per source: a tinted ground and a readable ink on it, one pair per store. */ internal class SourceColours(val fill: Color, val ink: Color, val dot: Color) @@ -39,16 +54,37 @@ internal fun sourceLabel(source: String): String = when (source) { else -> stringResource(R.string.source_added) } -/** The source chip itself; [small] for a list row, the regular size for a hero or a card. */ +/** The source's mark: the store's glyph (Simple Icons, CC0), Steam's, or a folder for a Custom game. */ @Composable -internal fun SourceChip(source: String, small: Boolean = false, modifier: Modifier = Modifier) { +internal fun sourcePainter(source: String): Painter = when (source) { + Store.GOG.id -> painterResource(R.drawable.ic_store_gog) + Store.EPIC.id -> painterResource(R.drawable.ic_store_epic) + Store.AMAZON.id -> painterResource(R.drawable.ic_store_amazon) + Library.SOURCE_STEAM -> painterResource(R.drawable.ic_store_steam) + else -> rememberVectorPainter(Icons.Outlined.CreateNewFolder) +} + +/** + * The source chip: a small rounded square in the source's colours holding its mark, the name as + * what a screen reader says. [withName] (a hero, a game page) puts the name beside the mark; + * [small] is a list row's size. + */ +@Composable +internal fun SourceChip(source: String, small: Boolean = false, withName: Boolean = false, modifier: Modifier = Modifier) { val c = sourceColours(source) - Text( - sourceLabel(source), fontSize = if (small) 10.sp else 11.sp, fontWeight = FontWeight.Bold, letterSpacing = 0.4.sp, - color = c.ink, maxLines = 1, - modifier = modifier.clip(RoundedCornerShape(6.dp)).background(c.fill) - .padding(horizontal = if (small) 6.dp else 8.dp, vertical = if (small) 1.dp else 2.dp), - ) + val name = sourceLabel(source) + val side = if (small) 22.dp else 24.dp + Row( + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(6.dp), + modifier = modifier.heightIn(min = side).clip(RoundedCornerShape(6.dp)).background(c.fill) + .then(if (withName) Modifier.padding(start = 4.dp, end = 8.dp) else Modifier.widthIn(min = side)) + .semantics(mergeDescendants = true) { contentDescription = name }, + ) { + Box(Modifier.size(side), contentAlignment = Alignment.Center) { + Icon(sourcePainter(source), null, tint = c.ink, modifier = Modifier.size(if (small) 14.dp else 16.dp)) + } + if (withName) Text(name, fontSize = 11.sp, fontWeight = FontWeight.Bold, letterSpacing = 0.4.sp, color = c.ink, maxLines = 1) + } } /** A status chip in the stores' colours: green for done ("Installed"), blue for busy, amber for a warning. */ diff --git a/app/src/main/java/com/droiddeck/launcher/ui/StoresDetail.kt b/app/src/main/java/com/droiddeck/launcher/ui/StoresDetail.kt index de3799600..b998f75f4 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/StoresDetail.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/StoresDetail.kt @@ -56,7 +56,7 @@ internal fun StoreGameDetail(store: Store, key: String, s: FrontEndState, a: Fro Rise(0) { Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp), modifier = Modifier.padding(bottom = 12.dp)) { BackLink(store.label, compact = true, onClick = onBack) - SourceChip(store.id) + SourceChip(store.id, withName = true) } } Rise(1) { diff --git a/app/src/main/java/com/droiddeck/launcher/ui/StoresPage.kt b/app/src/main/java/com/droiddeck/launcher/ui/StoresPage.kt index b0d61149e..35814052e 100644 --- a/app/src/main/java/com/droiddeck/launcher/ui/StoresPage.kt +++ b/app/src/main/java/com/droiddeck/launcher/ui/StoresPage.kt @@ -164,8 +164,8 @@ private fun StoreChips(selected: String, active: Int, onPick: (String) -> Unit, StoreChip( label = store.shortLabel, on = selected == store.id, key = store.id, modifier = Modifier.weight(1f), lead = { - val c = sourceColours(store.id) - Box(Modifier.size(12.dp).clip(CircleShape).background(c.dot).alpha(if (StoresState.isSignedIn(store)) 1f else 0.35f)) + // The store's mark in its colour; dimmed while signed out. + Icon(sourcePainter(store.id), null, tint = sourceColours(store.id).dot, modifier = Modifier.size(18.dp).alpha(if (StoresState.isSignedIn(store)) 1f else 0.4f)) }, description = store.label + if (StoresState.isSignedIn(store)) "" else " " + stringResource(R.string.stores_chip_signed_out), ) { onPick(store.id) } @@ -451,7 +451,10 @@ private fun GameCard(item: CatalogItem, store: Store, s: FrontEndState, a: Front .clip(RoundedCornerShape(10.dp)).background(colors.surface).glideBorder(hot, RoundedCornerShape(10.dp), pal.signal, pal.line) .hoverable(src).clickable(interactionSource = src, indication = LocalIndication.current, onClick = open).controllerConfirm(onClick = open), ) { - CardArt(item, Modifier.fillMaxWidth().aspectRatio(16f / 9f)) + Box { + CardArt(item, Modifier.fillMaxWidth().aspectRatio(16f / 9f)) + SourceChip(item.store.id, small = true, modifier = Modifier.align(Alignment.TopStart).padding(6.dp)) + } Column(verticalArrangement = Arrangement.spacedBy(5.dp), modifier = Modifier.padding(start = 8.dp, end = 8.dp, top = 6.dp, bottom = 8.dp)) { Text(item.title, fontSize = 11.sp, lineHeight = 13.sp, fontWeight = FontWeight.Bold, color = colors.onBackground, maxLines = 2, minLines = 2, overflow = TextOverflow.Ellipsis) PriceLine(item, installed) diff --git a/app/src/main/res/drawable/ic_store_amazon.xml b/app/src/main/res/drawable/ic_store_amazon.xml new file mode 100644 index 000000000..85d350553 --- /dev/null +++ b/app/src/main/res/drawable/ic_store_amazon.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_store_epic.xml b/app/src/main/res/drawable/ic_store_epic.xml new file mode 100644 index 000000000..35ba51beb --- /dev/null +++ b/app/src/main/res/drawable/ic_store_epic.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_store_gog.xml b/app/src/main/res/drawable/ic_store_gog.xml new file mode 100644 index 000000000..6329d3014 --- /dev/null +++ b/app/src/main/res/drawable/ic_store_gog.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_store_steam.xml b/app/src/main/res/drawable/ic_store_steam.xml new file mode 100644 index 000000000..296f87e30 --- /dev/null +++ b/app/src/main/res/drawable/ic_store_steam.xml @@ -0,0 +1,13 @@ + + + + + diff --git a/docs/development/stores.md b/docs/development/stores.md index 045192ead..4a9bfa464 100644 --- a/docs/development/stores.md +++ b/docs/development/stores.md @@ -79,8 +79,13 @@ 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`; it shows as a small +square chip with the source's mark (`SourceChip`) on Games rows, store cards and Downloads rows, and +mark + name in the Games hero and on a store game's page; the name is the chip's content +description. The marks are `res/drawable/ic_store_*.xml`, converted from Simple Icons v13.21.0 +(CC0-1.0; `gogdotcom`, `epicgames`, `amazongames`, `steam`); a Custom game uses Material's +CreateNewFolder. The store chips atop the Stores page carry the same marks (dimmed while signed +out). ## Launch diff --git a/docs/progress-log.md b/docs/progress-log.md index 4327a84cf..0ed870526 100644 --- a/docs/progress-log.md +++ b/docs/progress-log.md @@ -131,6 +131,10 @@ without it and Setup says "Stores engine not built"). `nativeAssemble` both). - **Install-to dialog compacted** (440 dp, two tiles that are the action, one-line caption, Cancel in the title row); the cog popup is capped at 440 dp too. +- **Source chips are marks now:** GOG / Epic / Amazon / Steam glyphs from Simple Icons (CC0) as + vector drawables, a folder-plus for Custom, in the existing per-source colours; icon-only on rows, + cards and Downloads, mark + name on the hero and game page; the Stores chip row's dots became the + marks too. - **Device-proven on the AYANEO Pocket FIT, 2026-10-08 (GOG):** sign-in, the library (33 owned), DOOM I Enhanced installed through the GOG engine to the SD Games root, shown in Games with the GOG chip and real art, registered as a non-Steam game in the Steam client (grid art present), and