Restore the Pro Libertate Games title and splash; list Katan under Board Games
Build and Release APK / Build APK and publish release (push) Canceled after 0s
Build and Release APK / Build APK and publish release (push) Canceled after 0s
The bootstrap rebranded the app to Pilgrims of Katan (WALA-118 review); the decision is to keep the upstream's own identity instead. - Title and splash: the upstream originals restored exactly — the Wallace tartan splash under the "Pro Libertate Games" uncial wordmark (SplashScreen.kt, Tartan.kt), the tartan launcher icon, and the Theme.ProLibertateGames window theme. The game screens go back to the upstream WallaceGold/WallaceRed accents the tartan is woven from. - Names and colours: launcher names back to "Pro Libertate Games" / "Pro Libertate Games DEV"; the channel colours back to the clan red / hunting green setts (resValue tartan_field and friends). The installed identity stays ca.wallacenet.pilgrimsofkatan (+ .dev) — that is the package, not the brand. - Catalogue: Katan is listed under Board Games (3-4 players, original blurb, no third-party marks) with available = false, so dev builds show it as "Coming soon" and a production build hides it until its engine and screen land. - Docs: README/CLAUDE.md/docs/CI.md updated to the restored identity; CatalogTest's invariant now names Katan as the one deliberate exception rather than asserting nothing is unfinished. Structural bootstrap untouched: applicationId, OTA repointed at the Gitea releases, CI workflows, version stamping. Verified: ./gradlew assembleDebug and testDebugUnitTest green — 628 tests, 0 failures. Co-Authored-By: Paperclip <[email protected]>
This commit is contained in:
co-authored by
Paperclip
parent
05c443a4b5
commit
5c797c8807
@@ -1,9 +1,9 @@
|
||||
# Working on this repo (agent notes)
|
||||
|
||||
Pilgrims of Katan — an Android collection of card and board games, playable
|
||||
against AI or over Wi-Fi (LAN). Bootstrapped from
|
||||
ProLibertateGames — Wallacenet's build of an Android collection of card and
|
||||
board games, playable against AI or over Wi-Fi (LAN). Upstream:
|
||||
[adrianoftyriel/ProLibertateGames](https://github.com/adrianoftyriel/ProLibertateGames)
|
||||
(GPL-3.0, kept as the `upstream` remote) for Wallacenet. Canonical repo:
|
||||
(GPL-3.0, kept as the `upstream` remote). Canonical repo:
|
||||
<https://git.wallacenet.ca/devadmin/PilgrimsOfKatan>.
|
||||
|
||||
## Ground rules
|
||||
@@ -11,13 +11,15 @@ against AI or over Wi-Fi (LAN). Bootstrapped from
|
||||
- **Licences travel.** `LICENSE` is GPL-3.0 and must stay. The Uncial Antiqua
|
||||
font ships with its OFL licence at `docs/licenses/UncialAntiqua-OFL.txt`;
|
||||
never delete or edit either. Attribution to the upstream lives in the README.
|
||||
- **No Catan trademarks or brand assets**, and none of the upstream's Wallace
|
||||
clan branding (it was removed in the rebrand; see README → Branding). The
|
||||
brand is: *Pilgrims of Katan*, `ca.wallacenet.pilgrimsofkatan` (+ `.dev`),
|
||||
slate blue / dev green, hexagon emblem.
|
||||
- **No Catan trademarks or brand assets** — the one hard line. The app's own
|
||||
branding is the upstream's, restored (see README → Branding): tartan launcher
|
||||
icon and splash, Wallace clan red on production and hunting green on dev,
|
||||
launcher names *Pro Libertate Games* / *Pro Libertate Games DEV*. The
|
||||
installed identity is `ca.wallacenet.pilgrimsofkatan` (+ `.dev`).
|
||||
- **The existing catalogue is the regression safety net.** Keep every game
|
||||
green; new games (Catan) arrive as separate tasks, not edits to old ones.
|
||||
All 22 games are `available = true`.
|
||||
green; new games (Katan) arrive as separate tasks, not edits to old ones.
|
||||
The 22 reference games are `available = true`; Katan is listed with
|
||||
`available = false` (*Coming soon*) until its game task lands.
|
||||
- **Docs conventions**: every game gets `docs/RULES-<id>.md` (index and gaps in
|
||||
`docs/README.md`); build/CI/release decisions go in `docs/CI.md`. Update the
|
||||
docs when behaviour changes — they are the check against the rules code.
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
# Pilgrims of Katan
|
||||
# ProLibertateGames
|
||||
|
||||
A collection of card and board games for Android, playable against the computer
|
||||
or against other people over Wi-Fi (LAN).
|
||||
|
||||
Derived from [ProLibertateGames](https://github.com/adrianoftyriel/ProLibertateGames)
|
||||
by adrianoftyriel, used under the GNU GPL v3 — see [LICENSE](LICENSE). Game
|
||||
rules documentation lives under [docs/](docs/README.md); build, CI and release
|
||||
decisions live in [docs/CI.md](docs/CI.md). Catan itself arrives in later work;
|
||||
this app ships the reference catalogue unchanged as the regression safety net.
|
||||
Wallacenet's build of [ProLibertateGames](https://github.com/adrianoftyriel/ProLibertateGames)
|
||||
by adrianoftyriel (GNU GPL v3 — see [LICENSE](LICENSE)), hosted at
|
||||
<https://git.wallacenet.ca/devadmin/PilgrimsOfKatan>. Game rules documentation
|
||||
lives under [docs/](docs/README.md); build, CI and release decisions live in
|
||||
[docs/CI.md](docs/CI.md). Katan itself arrives in later work — it is listed under
|
||||
Board Games as *Coming soon* — and this app ships the reference catalogue
|
||||
unchanged as the regression safety net.
|
||||
|
||||
## What works today
|
||||
|
||||
@@ -16,15 +18,15 @@ The app shell is complete and every game in the catalogue is playable end to end
|
||||
| | |
|
||||
| --- | --- |
|
||||
| **Cards** | Euchre, Kaiser, President, Golf, Wizard, Crazy 8s, Cribbage, Hearts, Klondike, FreeCell, Spider, Pyramid |
|
||||
| **Table** | Sequence, Chess, Checkers, Backgammon, Mastermind, Nine Men's Morris, Peg Solitaire, Yahtzee, Pirates and Bulgars, Ta Yü |
|
||||
| **Table** | Sequence, Chess, Checkers, Backgammon, Mastermind, Nine Men's Morris, Peg Solitaire, Yahtzee, Pirates and Bulgars, Ta Yü, Katan *(coming soon)* |
|
||||
|
||||
Nothing is marked *Coming soon* at the moment, because nothing is waiting. The
|
||||
machinery for it is still there and still tested: a game listed in `GameCatalog`
|
||||
with `available = false` appears in the menu marked *Coming soon* **on dev builds
|
||||
only**, so a production release lists just what can actually be played and nobody
|
||||
installs it and taps into a dead end. The menu decides this from the installed
|
||||
APK's own version name — the same source the updater uses to know which channel
|
||||
it is on.
|
||||
One entry is marked *Coming soon*: Katan, the island game, whose engine and
|
||||
screen land with their own task. The machinery is still there and still tested:
|
||||
a game listed in `GameCatalog` with `available = false` appears in the menu
|
||||
marked *Coming soon* **on dev builds only**, so a production release lists just
|
||||
what can actually be played and nobody installs it and taps into a dead end. The
|
||||
menu decides this from the installed APK's own version name — the same source
|
||||
the updater uses to know which channel it is on.
|
||||
|
||||
Adding a game means writing a `GameRules` implementation, a `GameAi` and a
|
||||
screen, then flipping `available` — the menu, lobby, networking, settings and OTA
|
||||
@@ -135,21 +137,21 @@ it: every rule above is a pure function and unit-tested on the JVM.
|
||||
|
||||
## Branding
|
||||
|
||||
Neutral, original, and deliberately *not* Catan's: no third-party marks or
|
||||
trade dress ship in this app (rebrand scope: WALA-118). The launcher icon is a
|
||||
hexagonal ring around a solid core — a game tile, not any game's logo — drawn
|
||||
as vector drawables so it stays sharp at every density and needs no PNGs. The
|
||||
app opens on the brand field fading to its deep shade, the name set in a Celtic
|
||||
uncial hand for two seconds before it fades into the menu.
|
||||
The app wears the upstream's own branding, restored (WALA-118 review): the
|
||||
Wallace tartan is the launcher icon and the splash, drawn as vector drawables and
|
||||
Compose code so it stays sharp at every density and needs no PNGs. The app opens
|
||||
on the tartan held under the name, set in a Celtic uncial hand for two seconds
|
||||
before it fades into the menu.
|
||||
|
||||
A dev build wears the **dev green** field where production wears **slate
|
||||
blue**, and installs as *Pilgrims of Katan DEV*. That is what tells the two
|
||||
A dev build wears the **hunting green** sett where production wears the **clan
|
||||
red**, and installs as *Pro Libertate Games DEV*. That is what tells the two
|
||||
copies apart when both are on the phone; see
|
||||
[Both channels on one device](#both-channels-on-one-device). The palette lives
|
||||
in `ui/theme/Theme.kt` (`BrandField`, `BrandDeep`, `BrandAccent`) and the two
|
||||
channel colours are generated per build by `resValue` in `app/build.gradle.kts`
|
||||
— the icon vectors reference `@color/brand_field` rather than a literal, which
|
||||
is how one set of vectors serves both colourways.
|
||||
[Both channels on one device](#both-channels-on-one-device). The tartan is drawn
|
||||
by `ui/theme/Tartan.kt` (`TartanBackground`, `threadWidthFor`) off
|
||||
`BuildConfig.DEV_BUILD`; the matching vector colours are generated per build by
|
||||
`resValue` in `app/build.gradle.kts` — the icon vectors reference
|
||||
`@color/tartan_field` rather than a literal, which is how one set of vectors
|
||||
serves both colourways.
|
||||
|
||||
Two honest caveats:
|
||||
|
||||
@@ -158,9 +160,10 @@ Two honest caveats:
|
||||
the SIL Open Font License 1.1. The full licence is at
|
||||
[docs/licenses/UncialAntiqua-OFL.txt](docs/licenses/UncialAntiqua-OFL.txt) and
|
||||
must stay with the font — the OFL requires it to be distributed alongside.
|
||||
- **The upstream's clan tartan is gone; its code is not.** The Wallace tartan
|
||||
was the upstream's brand and left with the rebrand. The GPL-licensed history
|
||||
that produced this app remains in git, and the source stays at
|
||||
- **Katan is the one place this app deliberately diverges.** No Catan
|
||||
trademarks or brand assets ship anywhere in it; the island game is listed
|
||||
under its own name with original art only. The GPL-licensed history that
|
||||
produced this app remains in git, and the source stays at
|
||||
[adrianoftyriel/ProLibertateGames](https://github.com/adrianoftyriel/ProLibertateGames)
|
||||
as the `upstream` remote.
|
||||
|
||||
@@ -187,10 +190,10 @@ smaller than a fingertip.
|
||||
Requires JDK 17 and the Android SDK (compileSdk 34).
|
||||
|
||||
A local build is a dev build: it installs as
|
||||
`ca.wallacenet.pilgrimsofkatan.dev` under the name *Pilgrims of Katan DEV* on
|
||||
the dev green field, so it goes on beside a production copy rather than over it.
|
||||
Set `PLG_CHANNEL=production` to build the production package
|
||||
(`ca.wallacenet.pilgrimsofkatan`, slate blue).
|
||||
`ca.wallacenet.pilgrimsofkatan.dev` under the name *Pro Libertate Games DEV* on
|
||||
the hunting-green sett, so it goes on beside a production copy rather than over
|
||||
it. Set `PLG_CHANNEL=production` to build the production package
|
||||
(`ca.wallacenet.pilgrimsofkatan`, clan red).
|
||||
|
||||
## CI
|
||||
|
||||
@@ -271,8 +274,8 @@ Three things move together, all off `isDevBuild` in `app/build.gradle.kts`:
|
||||
| | Production | Dev |
|
||||
| --- | --- | --- |
|
||||
| `applicationId` | `ca.wallacenet.pilgrimsofkatan` | `ca.wallacenet.pilgrimsofkatan.dev` |
|
||||
| Launcher name | Pilgrims of Katan | Pilgrims of Katan DEV |
|
||||
| Brand field | slate blue | deep green |
|
||||
| Launcher name | Pro Libertate Games | Pro Libertate Games DEV |
|
||||
| Tartan sett | clan red | hunting green |
|
||||
|
||||
Anything that is not an explicit `PLG_CHANNEL=production` build is a dev build,
|
||||
so a local `assembleDebug` is a dev build too and installs alongside a release
|
||||
|
||||
+17
-15
@@ -40,9 +40,9 @@ val versionSeries = (project.findProperty("plgVersionSeries") as String?) ?: "1.
|
||||
//
|
||||
// This one flag drives everything that makes a dev build a separate app rather
|
||||
// than a replacement for the production one — its own applicationId, its own
|
||||
// launcher name, and its own brand colour. They have to move together: a dev
|
||||
// package wearing the production name and colours is exactly the confusion
|
||||
// having both installed is meant to avoid.
|
||||
// launcher name, and the hunting sett in place of the clan sett. They have to
|
||||
// move together: a dev package wearing the production name and colours is
|
||||
// exactly the confusion having both installed is meant to avoid.
|
||||
val isDevBuild = buildChannel != "production"
|
||||
|
||||
android {
|
||||
@@ -83,19 +83,21 @@ android {
|
||||
resValue(
|
||||
"string",
|
||||
"app_name",
|
||||
if (isDevBuild) "Pilgrims of Katan DEV" else "Pilgrims of Katan",
|
||||
if (isDevBuild) "Pro Libertate Games DEV" else "Pro Libertate Games",
|
||||
)
|
||||
|
||||
// The one colour that separates the two installs: slate blue on
|
||||
// production, deep green on dev. The launcher icon and the splash are
|
||||
// both built from it, so the drawer shows at a glance which of the two
|
||||
// copies is which — the two have to stay in step or the icon and the
|
||||
// splash it opens on would be different brands.
|
||||
resValue("color", "brand_field", if (isDevBuild) "#2A6B54" else "#2B4A78")
|
||||
// A darker shade of the same hue, held behind the window for the
|
||||
// instant before the splash draws, so a launch never flashes the wrong
|
||||
// colour.
|
||||
resValue("color", "splash_background", if (isDevBuild) "#153B2F" else "#182A44")
|
||||
// The one colour that separates the two setts: Wallace red for
|
||||
// production, Wallace Hunting green for dev. The icon vectors reference
|
||||
// this rather than a literal, and Tartan.kt picks the matching Compose
|
||||
// colour off DEV_BUILD below — the two have to stay in step or the
|
||||
// launcher icon and the splash it opens on would be different cloth.
|
||||
resValue("color", "tartan_field", if (isDevBuild) "#1E7A3C" else "#E01B24")
|
||||
// Shades of the same field, so a launch does not flash the wrong colour
|
||||
// before the tartan is drawn: splash_background is held behind the
|
||||
// window for that instant, ic_launcher_background is the icon's flat
|
||||
// fallback.
|
||||
resValue("color", "splash_background", if (isDevBuild) "#0D2E17" else "#4A0B10")
|
||||
resValue("color", "ic_launcher_background", if (isDevBuild) "#145C2D" else "#B3121B")
|
||||
|
||||
buildConfigField("boolean", "DEV_BUILD", isDevBuild.toString())
|
||||
|
||||
@@ -131,7 +133,7 @@ android {
|
||||
|
||||
buildFeatures {
|
||||
compose = true
|
||||
// For DEV_BUILD, which is how the Compose splash picks its brand colour.
|
||||
// For DEV_BUILD, which is how the Compose tartan picks its sett.
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.PilgrimsOfKatan">
|
||||
android:theme="@style/Theme.ProLibertateGames">
|
||||
|
||||
<!-- No fixed orientation: the UI adapts to portrait, landscape and
|
||||
tablet widths. configChanges keeps Compose state across rotation
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.prolibertate.games.score.ScorekeeperRepository
|
||||
import org.prolibertate.games.settings.SettingsRepository
|
||||
import org.prolibertate.games.ui.AppEnv
|
||||
import org.prolibertate.games.ui.AppRoot
|
||||
import org.prolibertate.games.ui.theme.PilgrimsTheme
|
||||
import org.prolibertate.games.ui.theme.ProLibertateTheme
|
||||
import org.prolibertate.games.update.Updater
|
||||
import java.util.UUID
|
||||
|
||||
@@ -43,7 +43,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
|
||||
setContent {
|
||||
PilgrimsTheme {
|
||||
ProLibertateTheme {
|
||||
AppRoot(env)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,13 @@ object GameCatalog {
|
||||
const val MORRIS = "morris"
|
||||
const val PIRATES = "pirates"
|
||||
|
||||
/**
|
||||
* The island game the collection is named for. Listed under Board Games
|
||||
* and marked *Coming soon*; the engine and its screen land with the game
|
||||
* task, and `available` flips on once two-device play is verified.
|
||||
*/
|
||||
const val CATAN = "catan"
|
||||
|
||||
val all: List<GameDescriptor> = listOf(
|
||||
GameDescriptor(
|
||||
id = EUCHRE,
|
||||
@@ -333,6 +340,19 @@ object GameCatalog {
|
||||
"because your score multiplies them.",
|
||||
available = true,
|
||||
),
|
||||
GameDescriptor(
|
||||
id = CATAN,
|
||||
title = "Katan",
|
||||
menus = setOf(GameMenu.BOARD),
|
||||
minPlayers = 3,
|
||||
maxPlayers = 4,
|
||||
teamBased = false,
|
||||
blurb = "Trade, build, and settle the island.",
|
||||
// Listed, not playable: the engine and screen land with the Katan
|
||||
// game task. Dev builds show the entry as *Coming soon*; a
|
||||
// production build lists only what can actually be played.
|
||||
available = false,
|
||||
),
|
||||
)
|
||||
|
||||
fun byId(id: String): GameDescriptor? = all.firstOrNull { it.id == id }
|
||||
|
||||
@@ -4,6 +4,7 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -19,7 +20,6 @@ import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
@@ -30,9 +30,8 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import kotlinx.coroutines.delay
|
||||
import org.prolibertate.games.R
|
||||
import org.prolibertate.games.ui.theme.BrandAccent
|
||||
import org.prolibertate.games.ui.theme.BrandDeep
|
||||
import org.prolibertate.games.ui.theme.BrandField
|
||||
import org.prolibertate.games.ui.theme.TartanBackground
|
||||
import org.prolibertate.games.ui.theme.threadWidthFor
|
||||
|
||||
/** How long the splash holds at full opacity before fading. */
|
||||
private const val SPLASH_HOLD_MILLIS = 2_000L
|
||||
@@ -46,11 +45,7 @@ private const val SPLASH_FADE_MILLIS = 600
|
||||
val CelticFontFamily = FontFamily(Font(R.font.uncial_antiqua))
|
||||
|
||||
/**
|
||||
* The launch screen: the brand field under the name of the game.
|
||||
*
|
||||
* The gradient runs from the same hue as the window's `splash_background`
|
||||
* held behind it, so the first frame and the splash are one brand in two
|
||||
* shades rather than a colour change.
|
||||
* The launch screen: Wallace tartan under the name of the house.
|
||||
*
|
||||
* Drawn as an overlay rather than a separate destination, so the menu is
|
||||
* already composed and ready behind it and the fade reveals a live screen
|
||||
@@ -75,17 +70,30 @@ fun SplashScreen(
|
||||
onFinished()
|
||||
}
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.alpha(alpha)
|
||||
.background(Brush.verticalGradient(listOf(BrandField, BrandDeep))),
|
||||
) {
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize().alpha(alpha)) {
|
||||
// Read the constraints here and hold them in locals: inside Column the
|
||||
// implicit receiver becomes ColumnScope, and maxWidth/maxHeight are no
|
||||
// longer in scope.
|
||||
val shortestSide = minOf(maxWidth, maxHeight).value
|
||||
|
||||
// Weave scale is derived from the sett, not a fixed fraction, so a
|
||||
// couple of full repeats are always on screen whatever the threadcount
|
||||
// says and whatever the display is.
|
||||
val threadWidth = threadWidthFor(across = shortestSide.dp, repeats = 2f)
|
||||
val titleSize = (shortestSide * 0.13f).sp
|
||||
TartanBackground(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
threadWidth = threadWidth,
|
||||
// On the bias, matching the icon.
|
||||
rotationDegrees = 45f,
|
||||
)
|
||||
|
||||
// A scrim keeps the lettering legible over the busiest part of the sett.
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black.copy(alpha = 0.45f))
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
@@ -95,7 +103,7 @@ fun SplashScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Pilgrims",
|
||||
text = "Pro Libertate",
|
||||
style = TextStyle(
|
||||
fontFamily = CelticFontFamily,
|
||||
fontSize = titleSize,
|
||||
@@ -109,11 +117,11 @@ fun SplashScreen(
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "of Katan",
|
||||
text = "Games",
|
||||
style = TextStyle(
|
||||
fontFamily = CelticFontFamily,
|
||||
fontSize = titleSize,
|
||||
color = BrandAccent,
|
||||
color = Color(0xFFF2C200),
|
||||
textAlign = TextAlign.Center,
|
||||
shadow = Shadow(
|
||||
color = Color.Black,
|
||||
|
||||
@@ -19,7 +19,7 @@ import androidx.compose.ui.unit.dp
|
||||
import org.prolibertate.games.ui.common.CARD_ASPECT
|
||||
import org.prolibertate.games.ui.theme.FeltGreenDark
|
||||
import org.prolibertate.games.ui.theme.Parchment
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.WallaceGold
|
||||
|
||||
/**
|
||||
* The furniture every patience table needs.
|
||||
@@ -48,7 +48,7 @@ fun TableSlot(
|
||||
modifier = modifier
|
||||
.width(width)
|
||||
.border(
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, AccentGold),
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, WallaceGold),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
)
|
||||
.clickable { onClick() },
|
||||
|
||||
@@ -48,7 +48,7 @@ import org.prolibertate.games.ui.common.CardBackView
|
||||
import org.prolibertate.games.ui.common.PlayingCardView
|
||||
import org.prolibertate.games.ui.theme.FeltGreenDark
|
||||
import org.prolibertate.games.ui.theme.Parchment
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.WallaceGold
|
||||
|
||||
private const val TITLE = "Klondike"
|
||||
|
||||
@@ -311,7 +311,7 @@ private fun Slot(
|
||||
modifier = modifier
|
||||
.width(CARD_WIDTH)
|
||||
.border(
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, AccentGold),
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, WallaceGold),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
)
|
||||
.clickable { onClick() },
|
||||
|
||||
@@ -41,8 +41,8 @@ import org.prolibertate.games.net.MatchController
|
||||
import org.prolibertate.games.ui.ScreenScaffold
|
||||
import org.prolibertate.games.ui.theme.FeltGreenDark
|
||||
import org.prolibertate.games.ui.theme.Parchment
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.AccentRed
|
||||
import org.prolibertate.games.ui.theme.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.WallaceRed
|
||||
|
||||
private const val TITLE = "Peg Solitaire"
|
||||
|
||||
@@ -214,15 +214,15 @@ private fun HoleView(
|
||||
Box(modifier = modifier.size(size), contentAlignment = Alignment.Center) {
|
||||
val diameter = size * 0.72f
|
||||
val ring = when {
|
||||
selected -> AccentGold
|
||||
isDestination -> AccentGold
|
||||
selected -> WallaceGold
|
||||
isDestination -> WallaceGold
|
||||
else -> Parchment.copy(alpha = 0.35f)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(diameter)
|
||||
.background(
|
||||
color = if (filled) AccentRed else FeltGreenDark,
|
||||
color = if (filled) WallaceRed else FeltGreenDark,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.border(
|
||||
|
||||
@@ -42,8 +42,8 @@ import org.prolibertate.games.game.yahtzee.scoreOf
|
||||
import org.prolibertate.games.net.MatchController
|
||||
import org.prolibertate.games.ui.ScreenScaffold
|
||||
import org.prolibertate.games.ui.theme.Parchment
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.AccentRed
|
||||
import org.prolibertate.games.ui.theme.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.WallaceRed
|
||||
|
||||
private const val TITLE = "Yahtzee"
|
||||
|
||||
@@ -183,7 +183,7 @@ private fun Die(face: Int?, held: Boolean, enabled: Boolean, onClick: () -> Unit
|
||||
.background(if (face == null) Parchment.copy(alpha = 0.3f) else Parchment, RoundedCornerShape(8.dp))
|
||||
.border(
|
||||
width = if (held) 3.dp else 1.dp,
|
||||
color = if (held) AccentGold else AccentRed.copy(alpha = 0.4f),
|
||||
color = if (held) WallaceGold else WallaceRed.copy(alpha = 0.4f),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
)
|
||||
.clickable(enabled = enabled) { onClick() },
|
||||
@@ -193,7 +193,7 @@ private fun Die(face: Int?, held: Boolean, enabled: Boolean, onClick: () -> Unit
|
||||
text = face?.toString() ?: "·",
|
||||
style = MaterialTheme.typography.titleLarge,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = AccentRed,
|
||||
color = WallaceRed,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -263,7 +263,7 @@ private fun CardRow(
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (written == null && open) AccentRed else MaterialTheme.colorScheme.onSurface,
|
||||
color = if (written == null && open) WallaceRed else MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,187 @@
|
||||
package org.prolibertate.games.ui.theme
|
||||
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.geometry.Size
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.graphics.drawscope.rotate
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.prolibertate.games.BuildConfig
|
||||
|
||||
/** One band of a tartan sett, measured in threads. */
|
||||
data class TartanStripe(val color: Color, val threads: Int)
|
||||
|
||||
val WallaceRed = Color(0xFFE01B24)
|
||||
val WallaceBlack = Color(0xFF0C0C0C)
|
||||
val WallaceGold = Color(0xFFF2C518)
|
||||
|
||||
/**
|
||||
* The green the hunting sett puts where the clan sett puts red.
|
||||
*
|
||||
* Must match `tartan_field` for a dev build in app/build.gradle.kts, which is
|
||||
* what the launcher icon is woven from — otherwise the icon and the splash it
|
||||
* opens on would be visibly different cloth.
|
||||
*/
|
||||
val WallaceHuntingGreen = Color(0xFF1E7A3C)
|
||||
|
||||
/**
|
||||
* Expands a half-sett into a full repeat by reflecting it.
|
||||
*
|
||||
* Tartans are specified as a half-sett between two pivots and woven out to
|
||||
* both sides, so `A B C` becomes `A B C B` — the pivot stripes are not
|
||||
* doubled. Writing the data this way keeps it in the same form a threadcount
|
||||
* is published in, instead of a flat list that has to be kept symmetric by
|
||||
* hand.
|
||||
*/
|
||||
fun List<TartanStripe>.pivoted(): List<TartanStripe> =
|
||||
if (size < 3) this else this + drop(1).dropLast(1).reversed()
|
||||
|
||||
/**
|
||||
* The Wallace sett, from its published threadcount: **K/4 R32 K32 Y/4**.
|
||||
*
|
||||
* The slashes mark the pivots, so this is the half-sett and it reflects out to
|
||||
* `K4 R32 K32 Y4 K32 R32` — 136 threads, roughly half black, half red, with a
|
||||
* yellow line worth 3%. Two consequences that are easy to get backwards and
|
||||
* that define the pattern:
|
||||
*
|
||||
* - the yellow overstripe runs down the **centre of a wide black band**, not
|
||||
* between red and black;
|
||||
* - the narrow black guard sits **between two red blocks**, splitting the red
|
||||
* into pairs.
|
||||
*
|
||||
* The threadcount itself lives in [wallaceSett], because the hunting colourway
|
||||
* below is the same cloth in a different colour and must not drift from it.
|
||||
*/
|
||||
val WallaceSett: List<TartanStripe> = wallaceSett(WallaceRed)
|
||||
|
||||
/**
|
||||
* Wallace Hunting: the same threadcount woven in green.
|
||||
*
|
||||
* Hunting setts are the clan cloth in muted, outdoor colours, and for Wallace
|
||||
* that is the one substitution — green for the red — with the black ground and
|
||||
* the yellow overstripe untouched. Deriving it from the same builder rather
|
||||
* than writing the numbers out twice is what keeps that true: a change to the
|
||||
* threadcount cannot land in one colourway and miss the other.
|
||||
*/
|
||||
val WallaceHuntingSett: List<TartanStripe> = wallaceSett(WallaceHuntingGreen)
|
||||
|
||||
/** The Wallace threadcount woven with [field] in place of the red. */
|
||||
private fun wallaceSett(field: Color): List<TartanStripe> = listOf(
|
||||
TartanStripe(WallaceBlack, 4),
|
||||
TartanStripe(field, 32),
|
||||
TartanStripe(WallaceBlack, 32),
|
||||
TartanStripe(WallaceGold, 4),
|
||||
).pivoted()
|
||||
|
||||
/**
|
||||
* The cloth this build wears: the clan sett on production, the hunting sett on
|
||||
* dev.
|
||||
*
|
||||
* A dev build installs as its own app beside the production one, so it has to
|
||||
* be recognisable at a glance in the app drawer and on the splash — the green
|
||||
* says which of the two copies just opened without anything having to be read.
|
||||
*/
|
||||
val AppSett: List<TartanStripe> =
|
||||
if (BuildConfig.DEV_BUILD) WallaceHuntingSett else WallaceSett
|
||||
|
||||
/** Threads in one full repeat of a sett. */
|
||||
val List<TartanStripe>.threadsPerRepeat: Int get() = sumOf { it.threads }
|
||||
|
||||
/**
|
||||
* The thread width that fits [repeats] repeats of [sett] across [across].
|
||||
*
|
||||
* Scale has to be derived from the sett rather than fixed, because changing a
|
||||
* threadcount changes how many threads a repeat contains — a fixed width and a
|
||||
* larger sett silently zooms in until the cloth is a couple of enormous blocks
|
||||
* and stops reading as tartan at all.
|
||||
*/
|
||||
fun threadWidthFor(
|
||||
sett: List<TartanStripe> = AppSett,
|
||||
across: Dp,
|
||||
repeats: Float = 2f,
|
||||
): Dp = across / (sett.threadsPerRepeat * repeats)
|
||||
|
||||
/**
|
||||
* Draws a tartan by laying the sett down vertically as warp, then across as
|
||||
* weft at half opacity. The overlap is what produces the blended squares and
|
||||
* the sense of a woven cloth rather than a printed grid — red warp under black
|
||||
* weft comes out a darker red, exactly as it does on the loom.
|
||||
*
|
||||
* [rotationDegrees] turns the cloth on the bias. The stripes are drawn well
|
||||
* past the edges of the canvas so the corners stay covered once it is turned.
|
||||
*/
|
||||
fun DrawScope.drawTartan(
|
||||
sett: List<TartanStripe> = AppSett,
|
||||
threadSize: Float,
|
||||
rotationDegrees: Float = 0f,
|
||||
) {
|
||||
if (sett.isEmpty() || threadSize <= 0f) return
|
||||
val repeat = sett.sumOf { it.threads } * threadSize
|
||||
if (repeat <= 0f) return
|
||||
|
||||
// Turning a square by 45 degrees needs about 0.21 of its size spare on
|
||||
// every edge; three quarters is generous enough for any angle.
|
||||
val pad = if (rotationDegrees == 0f) 0f else size.maxDimension * 0.75f
|
||||
val left = -pad
|
||||
val top = -pad
|
||||
val right = size.width + pad
|
||||
val bottom = size.height + pad
|
||||
|
||||
rotate(degrees = rotationDegrees) {
|
||||
// Warp: full-height bands.
|
||||
var x = left - (left.mod(repeat))
|
||||
while (x < right) {
|
||||
var offset = x
|
||||
for (stripe in sett) {
|
||||
val width = stripe.threads * threadSize
|
||||
drawRect(
|
||||
color = stripe.color,
|
||||
topLeft = Offset(offset, top),
|
||||
size = Size(width, bottom - top),
|
||||
)
|
||||
offset += width
|
||||
}
|
||||
x += repeat
|
||||
}
|
||||
|
||||
// Weft: full-width bands, half opacity so the warp shows through.
|
||||
var y = top - (top.mod(repeat))
|
||||
while (y < bottom) {
|
||||
var offset = y
|
||||
for (stripe in sett) {
|
||||
val height = stripe.threads * threadSize
|
||||
drawRect(
|
||||
color = stripe.color.copy(alpha = 0.5f),
|
||||
topLeft = Offset(left, offset),
|
||||
size = Size(right - left, height),
|
||||
)
|
||||
offset += height
|
||||
}
|
||||
y += repeat
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A tartan fill. [threadWidth] sets the weave scale, so the same cloth can be
|
||||
* a fine weave on a phone and a coarse one on a tablet without redrawing it.
|
||||
*/
|
||||
@Composable
|
||||
fun TartanBackground(
|
||||
modifier: Modifier = Modifier,
|
||||
sett: List<TartanStripe> = AppSett,
|
||||
threadWidth: Dp = 2.dp,
|
||||
rotationDegrees: Float = 0f,
|
||||
) {
|
||||
Canvas(modifier = modifier) {
|
||||
drawTartan(
|
||||
sett = sett,
|
||||
threadSize = threadWidth.toPx(),
|
||||
rotationDegrees = rotationDegrees,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.material3.darkColorScheme
|
||||
import androidx.compose.material3.lightColorScheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import org.prolibertate.games.BuildConfig
|
||||
|
||||
/** Card-table green, which the board and table surfaces are drawn on. */
|
||||
val FeltGreen = Color(0xFF1B5E20)
|
||||
@@ -15,31 +14,6 @@ val Parchment = Color(0xFFF4EFE4)
|
||||
val CardRed = Color(0xFFB3261E)
|
||||
val CardBlack = Color(0xFF1C1B1F)
|
||||
|
||||
/**
|
||||
* The brand palette for Pilgrims of Katan — original and neutral, shared by
|
||||
* every game in the catalogue. Slate blue on production, deep green on dev, so
|
||||
* the two installs are told apart at a glance; both must stay in step with the
|
||||
* resValues in app/build.gradle.kts or the icon and the splash it opens on
|
||||
* would be different brands.
|
||||
*
|
||||
* The upstream shipped a clan tartan here. This project carries no clan or
|
||||
* third-party trade dress (WALA-118).
|
||||
*/
|
||||
val BrandField: Color = if (BuildConfig.DEV_BUILD) Color(0xFF2A6B54) else Color(0xFF2B4A78)
|
||||
val BrandDeep: Color = if (BuildConfig.DEV_BUILD) Color(0xFF153B2F) else Color(0xFF182A44)
|
||||
|
||||
/** The parchment accent: the emblem on the icon, the second line of the splash. */
|
||||
val BrandAccent: Color = Color(0xFFEFD9A4)
|
||||
|
||||
/**
|
||||
* Accents used *inside* a game table — a highlight, a held die, a danger mark.
|
||||
* Deliberately channel-free: what a game shows must not change with which
|
||||
* install it is running in. Values unchanged from the upstream, so the games
|
||||
* themselves look exactly as they did.
|
||||
*/
|
||||
val AccentRed = Color(0xFFE01B24)
|
||||
val AccentGold = Color(0xFFF2C518)
|
||||
|
||||
val TeamColours: List<Color> = listOf(
|
||||
Color(0xFF1565C0), // blue
|
||||
Color(0xFFC62828), // red
|
||||
@@ -65,7 +39,7 @@ private val LightColours = lightColorScheme(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun PilgrimsTheme(
|
||||
fun ProLibertateTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
||||
@@ -1,16 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The flat field behind the launcher emblem: brand_field, slate blue on
|
||||
production and deep green on dev, generated by the build (see resValue in
|
||||
app/build.gradle.kts) so the two installs are told apart in the app drawer
|
||||
by the icon alone.
|
||||
A single square of the Wallace tartan, set on the bias.
|
||||
|
||||
Generated from the published threadcount K/4 R32 K32 Y/4 — the same numbers
|
||||
as WallaceSett in Tartan.kt. The slashes are pivots, so the half-sett
|
||||
reflects out to K4 R32 K32 Y4 K32 R32: the yellow overstripe runs down the
|
||||
centre of a wide black band, and the narrow black guard splits the red into
|
||||
pairs.
|
||||
|
||||
The stripes reference @color/tartan_field rather than a literal, because the
|
||||
same threadcount is woven in two colourways: Wallace red on a production
|
||||
build, Wallace Hunting green on a dev one, so the two installs are told apart
|
||||
in the app drawer by the icon alone. The build generates that colour; see
|
||||
resValue in app/build.gradle.kts.
|
||||
|
||||
Warp is opaque, weft laid over it at half opacity, which is what gives the
|
||||
blended squares rather than a flat grid. Paths run well past the viewport
|
||||
because the group is rotated 45 degrees; the overspill keeps the corners
|
||||
covered. One full repeat fills the adaptive icon's safe zone.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="@color/brand_field"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<group
|
||||
android:rotation="45"
|
||||
android:pivotX="54.0"
|
||||
android:pivotY="54.0">
|
||||
<path android:fillColor="@color/tartan_ground" android:pathData="M-90.00,-90.00h288.00v288.00h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-88.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-69.88,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M-36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-16.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M2.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M55.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M74.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M108.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M127.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M146.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M180.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-105.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-88.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-69.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-52.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,-36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-33.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-16.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,0.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,2.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,19.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,38.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,55.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,74.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,91.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,108.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,110.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,127.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,144.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,146.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,163.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,180.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,182.12h288.00v16.94h-288.00z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The brand emblem: a hexagonal ring around a solid hexagonal core, in
|
||||
parchment on the brand field behind it (see ic_launcher_background).
|
||||
|
||||
The hexagon is a game tile, not any game's mark — deliberately generic
|
||||
branding for Pilgrims of Katan, carrying no third-party logo or trade dress.
|
||||
Everything sits inside the adaptive icon's safe zone (a centred 66dp circle
|
||||
on this 108dp canvas) so no launcher mask can clip it.
|
||||
Deliberately empty. The icon is the cloth itself, so the adaptive icon's
|
||||
foreground layer carries nothing — anything drawn here would sit on top of the
|
||||
tartan and break the "single square of tartan" idea.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<!-- Ring and core in one even-odd path: outer hexagon, inner hexagon,
|
||||
core hexagon — odd coverage fills, so the ring and the core are drawn
|
||||
and the gap between them stays open to the field showing through. -->
|
||||
<path
|
||||
android:fillColor="@color/brand_accent"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M84,54 L69,79.98 L39,79.98 L24,54 L39,28.02 L69,28.02 Z M72,54 L63,69.59 L45,69.59 L36,54 L45,38.41 L63,38.41 Z M63,54 L58.5,61.79 L49.5,61.79 L45,54 L49.5,46.21 L58.5,46.21 Z" />
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,22 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The legacy (pre-API 26) launcher icon: the same brand emblem as the adaptive
|
||||
icon's foreground, over the same brand field, in the one layer those
|
||||
launchers get.
|
||||
A single square of the Wallace tartan, set on the bias.
|
||||
|
||||
Field first, emblem second: two paths, because the emblem's ring relies on
|
||||
even-odd parity and a filled background inside the same path would flip it.
|
||||
Generated from the published threadcount K/4 R32 K32 Y/4 — the same numbers
|
||||
as WallaceSett in Tartan.kt. The slashes are pivots, so the half-sett
|
||||
reflects out to K4 R32 K32 Y4 K32 R32: the yellow overstripe runs down the
|
||||
centre of a wide black band, and the narrow black guard splits the red into
|
||||
pairs.
|
||||
|
||||
The stripes reference @color/tartan_field rather than a literal, because the
|
||||
same threadcount is woven in two colourways: Wallace red on a production
|
||||
build, Wallace Hunting green on a dev one, so the two installs are told apart
|
||||
in the app drawer by the icon alone. The build generates that colour; see
|
||||
resValue in app/build.gradle.kts.
|
||||
|
||||
Warp is opaque, weft laid over it at half opacity, which is what gives the
|
||||
blended squares rather than a flat grid. Paths run well past the viewport
|
||||
because the group is rotated 45 degrees; the overspill keeps the corners
|
||||
covered. One full repeat fills the adaptive icon's safe zone.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="@color/brand_field"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="@color/brand_accent"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M84,54 L69,79.98 L39,79.98 L24,54 L39,28.02 L69,28.02 Z M72,54 L63,69.59 L45,69.59 L36,54 L45,38.41 L63,38.41 Z M63,54 L58.5,61.79 L49.5,61.79 L45,54 L49.5,46.21 L58.5,46.21 Z" />
|
||||
<group
|
||||
android:rotation="45"
|
||||
android:pivotX="54.0"
|
||||
android:pivotY="54.0">
|
||||
<path android:fillColor="@color/tartan_ground" android:pathData="M-90.00,-90.00h288.00v288.00h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-88.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-69.88,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M-36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-16.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M2.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M55.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M74.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M108.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M127.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M146.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M180.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-105.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-88.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-69.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-52.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,-36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-33.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-16.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,0.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,2.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,19.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,38.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,55.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,74.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,91.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,108.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,110.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,127.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,144.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,146.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,163.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,180.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,182.12h288.00v16.94h-288.00z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,22 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The legacy (pre-API 26) launcher icon: the same brand emblem as the adaptive
|
||||
icon's foreground, over the same brand field, in the one layer those
|
||||
launchers get.
|
||||
A single square of the Wallace tartan, set on the bias.
|
||||
|
||||
Field first, emblem second: two paths, because the emblem's ring relies on
|
||||
even-odd parity and a filled background inside the same path would flip it.
|
||||
Generated from the published threadcount K/4 R32 K32 Y/4 — the same numbers
|
||||
as WallaceSett in Tartan.kt. The slashes are pivots, so the half-sett
|
||||
reflects out to K4 R32 K32 Y4 K32 R32: the yellow overstripe runs down the
|
||||
centre of a wide black band, and the narrow black guard splits the red into
|
||||
pairs.
|
||||
|
||||
The stripes reference @color/tartan_field rather than a literal, because the
|
||||
same threadcount is woven in two colourways: Wallace red on a production
|
||||
build, Wallace Hunting green on a dev one, so the two installs are told apart
|
||||
in the app drawer by the icon alone. The build generates that colour; see
|
||||
resValue in app/build.gradle.kts.
|
||||
|
||||
Warp is opaque, weft laid over it at half opacity, which is what gives the
|
||||
blended squares rather than a flat grid. Paths run well past the viewport
|
||||
because the group is rotated 45 degrees; the overspill keeps the corners
|
||||
covered. One full repeat fills the adaptive icon's safe zone.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="@color/brand_field"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="@color/brand_accent"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M84,54 L69,79.98 L39,79.98 L24,54 L39,28.02 L69,28.02 Z M72,54 L63,69.59 L45,69.59 L36,54 L45,38.41 L63,38.41 Z M63,54 L58.5,61.79 L49.5,61.79 L45,54 L49.5,46.21 L58.5,46.21 Z" />
|
||||
<group
|
||||
android:rotation="45"
|
||||
android:pivotX="54.0"
|
||||
android:pivotY="54.0">
|
||||
<path android:fillColor="@color/tartan_ground" android:pathData="M-90.00,-90.00h288.00v288.00h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-88.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-69.88,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M-36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M-16.94,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M2.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M36.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M55.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M74.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M108.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M127.06,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_field" android:pathData="M146.12,-90.00h16.94v288.00h-16.94z" />
|
||||
<path android:fillColor="@color/tartan_over" android:pathData="M180.00,-90.00h2.12v288.00h-2.12z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-105.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-88.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-69.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-52.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,-36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,-33.88h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,-16.94h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,0.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,2.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,19.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,36.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,38.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,55.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,72.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,74.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,91.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,108.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,110.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,127.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,144.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_field" android:fillAlpha="0.5" android:pathData="M-90.00,146.12h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,163.06h288.00v16.94h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_over" android:fillAlpha="0.5" android:pathData="M-90.00,180.00h288.00v2.12h-288.00z" />
|
||||
<path android:fillColor="@color/tartan_ground" android:fillAlpha="0.5" android:pathData="M-90.00,182.12h288.00v16.94h-288.00z" />
|
||||
</group>
|
||||
</vector>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
The colours the brand shares between channels. The two that differ —
|
||||
brand_field (slate blue on production, deep green on dev) and its darker
|
||||
shade splash_background — are generated by the build. See resValue in
|
||||
app/build.gradle.kts.
|
||||
The colours the sett shares between channels. The one that differs —
|
||||
tartan_field, red on production and hunting green on dev — is generated by the
|
||||
build, along with splash_background and ic_launcher_background, which are
|
||||
shades of it. See resValue in app/build.gradle.kts.
|
||||
-->
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<!-- The parchment accent drawn on the brand field: the emblem on the
|
||||
launcher icon and the second line of the splash. Same number as the
|
||||
accent in Theme.kt. -->
|
||||
<color name="brand_accent">#EFD9A4</color>
|
||||
<!-- The ground the sett is woven on, and the overstripe down its centre.
|
||||
Same numbers as WallaceBlack and WallaceGold in Tartan.kt. -->
|
||||
<color name="tartan_ground">#0C0C0C</color>
|
||||
<color name="tartan_over">#F2C518</color>
|
||||
</resources>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
app_name is generated by the build rather than declared here, because it
|
||||
differs by channel: a dev build installs as "Pilgrims of Katan DEV"
|
||||
differs by channel: a dev build installs as "Pro Libertate Games DEV"
|
||||
alongside the production copy. See resValue in app/build.gradle.kts.
|
||||
-->
|
||||
<resources>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<resources>
|
||||
<!-- Compose supplies the real theme; this only needs to be a Material 3
|
||||
base with no action bar so the app draws its own top bars. -->
|
||||
<style name="Theme.PilgrimsOfKatan" parent="android:Theme.Material.NoActionBar">
|
||||
<style name="Theme.ProLibertateGames" parent="android:Theme.Material.NoActionBar">
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowBackground">@color/splash_background</item>
|
||||
|
||||
@@ -106,17 +106,19 @@ class CatalogTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `every game in the catalogue can be played`() {
|
||||
// There is nothing left marked "coming soon". The machinery that hides
|
||||
// unfinished games from a production build is still there and still
|
||||
// tested above — it simply has nothing to hide at the moment, and this
|
||||
// is what will fail the day something is added to the list before its
|
||||
// engine is written.
|
||||
assertTrue(GameCatalog.all.all { it.available })
|
||||
fun `every reference game in the catalogue can be played`() {
|
||||
// Every game the app ships is playable. The one exception is Katan,
|
||||
// listed so the slot exists and marked *Coming soon* until its engine
|
||||
// and screen land — `available = false` is what keeps an unfinished
|
||||
// game out of a production build while leaving it visible on a dev one.
|
||||
assertEquals(
|
||||
GameCatalog.inMenu(GameMenu.BOARD, includeComingSoon = false),
|
||||
GameCatalog.inMenu(GameMenu.BOARD, includeComingSoon = true),
|
||||
listOf(GameCatalog.CATAN),
|
||||
GameCatalog.all.filterNot { it.available }.map { it.id },
|
||||
)
|
||||
// It is also the only difference the two channels see in a menu.
|
||||
val hiddenFromProduction = GameCatalog.inMenu(GameMenu.BOARD, includeComingSoon = true) -
|
||||
GameCatalog.inMenu(GameMenu.BOARD, includeComingSoon = false)
|
||||
assertEquals(listOf(GameCatalog.byId(GameCatalog.CATAN)!!), hiddenFromProduction)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
+2
-2
@@ -87,8 +87,8 @@ PLG_CHANNEL=production ./gradlew assembleDebug # production app (ca.wallacenet
|
||||
```
|
||||
|
||||
The two variants install side by side: different applicationId, different
|
||||
launcher name (`Pilgrims of Katan DEV` vs `Pilgrims of Katan`), different brand
|
||||
colour.
|
||||
launcher name (`Pro Libertate Games DEV` vs `Pro Libertate Games`), different
|
||||
tartan sett.
|
||||
|
||||
## Manual release (works today, no runner needed)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user