Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d7cd6182f | ||
|
|
b58b4251dc | ||
|
|
5c797c8807 | ||
|
|
05c443a4b5 |
@@ -69,7 +69,7 @@ jobs:
|
||||
- name: Upload debug APK
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PilgrimsOfKatan-debug-apk
|
||||
name: ProLibertateGames-debug-apk
|
||||
path: app/build/outputs/apk/debug/app-debug.apk
|
||||
if-no-files-found: error
|
||||
retention-days: 14
|
||||
@@ -79,7 +79,7 @@ jobs:
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PilgrimsOfKatan-reports
|
||||
name: ProLibertateGames-reports
|
||||
path: |
|
||||
app/build/reports/
|
||||
if-no-files-found: ignore
|
||||
@@ -109,7 +109,7 @@ jobs:
|
||||
stamp="$(( ${{ github.run_number }} + offset ))"
|
||||
version="v${series}.${stamp}-dev"
|
||||
src="app/build/outputs/apk/debug/app-debug.apk"
|
||||
dest="PilgrimsOfKatan-${version}.apk"
|
||||
dest="ProLibertateGames-${version}.apk"
|
||||
cp "$src" "$dest"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "apk=$dest" >> "$GITHUB_OUTPUT"
|
||||
|
||||
@@ -64,7 +64,7 @@ jobs:
|
||||
stamp="$(( ${{ github.run_number }} + offset ))"
|
||||
version="v${series}.${stamp}"
|
||||
src="app/build/outputs/apk/debug/app-debug.apk"
|
||||
dest="PilgrimsOfKatan-${version}.apk"
|
||||
dest="ProLibertateGames-${version}.apk"
|
||||
cp "$src" "$dest"
|
||||
echo "version=$version" >> "$GITHUB_OUTPUT"
|
||||
echo "apk=$dest" >> "$GITHUB_OUTPUT"
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Upload APK as workflow artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: PilgrimsOfKatan-apk
|
||||
name: ProLibertateGames-apk
|
||||
path: ${{ steps.stage.outputs.apk }}
|
||||
if-no-files-found: error
|
||||
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
# 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:
|
||||
<https://git.wallacenet.ca/devadmin/PilgrimsOfKatan>.
|
||||
(GPL-3.0, kept as the `upstream` remote). Canonical repo:
|
||||
<https://git.wallacenet.ca/devadmin/ProLibertateGames>.
|
||||
|
||||
## Ground rules
|
||||
|
||||
- **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.prolibertategames` (+ `.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.
|
||||
@@ -44,7 +46,7 @@ One flag — `isDevBuild` in `app/build.gradle.kts` — drives applicationId,
|
||||
launcher name and brand colour together. Never set them individually.
|
||||
|
||||
In-app OTA points at this repo's own Gitea releases
|
||||
(`/api/v1/repos/devadmin/PilgrimsOfKatan/releases…`) and is **on**:
|
||||
(`/api/v1/repos/devadmin/ProLibertateGames/releases…`) and is **on**:
|
||||
`BuildConfig.OTA_ENABLED` is `true`. The channel is a decision already made and
|
||||
recorded — `docs/CI.md` documents the endpoints, the build-number scheme, and
|
||||
what publishing requires. Setting the flag to `false` is the kill switch.
|
||||
|
||||
@@ -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/ProLibertateGames>. 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
|
||||
|
||||
@@ -15,16 +17,16 @@ 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 |
|
||||
| **Board** | Sequence, Chess, Checkers, Backgammon, Mastermind, Nine Men's Morris, Pirates and Bulgars, Ta Yü |
|
||||
| **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ü, 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.prolibertategames.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.prolibertategames`, clan red).
|
||||
|
||||
## CI
|
||||
|
||||
@@ -262,7 +265,7 @@ unit-tested on the JVM like the rules engines are.
|
||||
### Both channels on one device
|
||||
|
||||
A dev build installs under its own `applicationId`,
|
||||
`ca.wallacenet.pilgrimsofkatan.dev`, so it is a separate app as far as Android
|
||||
`ca.wallacenet.prolibertategames.dev`, so it is a separate app as far as Android
|
||||
is concerned and can sit beside the production copy rather than replacing it. Each channel still updates itself
|
||||
normally, because an update within a channel keeps the same package.
|
||||
|
||||
@@ -270,9 +273,9 @@ 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 |
|
||||
| `applicationId` | `ca.wallacenet.prolibertategames` | `ca.wallacenet.prolibertategames.dev` |
|
||||
| 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
|
||||
|
||||
+18
-16
@@ -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 {
|
||||
@@ -58,7 +58,7 @@ android {
|
||||
// A dev build installs under its own package, so a dev copy and a
|
||||
// production copy can sit side by side on one device instead of one
|
||||
// replacing the other.
|
||||
applicationId = if (isDevBuild) "ca.wallacenet.pilgrimsofkatan.dev" else "ca.wallacenet.pilgrimsofkatan"
|
||||
applicationId = if (isDevBuild) "ca.wallacenet.prolibertategames.dev" else "ca.wallacenet.prolibertategames"
|
||||
minSdk = 24
|
||||
targetSdk = 34
|
||||
// The -dev suffix in versionName is what the installed app reads to
|
||||
@@ -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 }
|
||||
|
||||
@@ -308,6 +308,6 @@ class LanTransport(private val context: Context) : Transport {
|
||||
}
|
||||
|
||||
const val CONNECT_TIMEOUT_MS = 8_000
|
||||
const val WIFI_LOCK_TAG = "PilgrimsOfKatan:lan"
|
||||
const val WIFI_LOCK_TAG = "ProLibertateGames:lan"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ const val PURPOSE_SCOREKEEPER = "scorekeeper"
|
||||
|
||||
/** Service type and name advertised over mDNS. */
|
||||
const val SERVICE_TYPE = "_plgames._tcp"
|
||||
const val SERVICE_NAME = "PilgrimsOfKatan"
|
||||
const val SERVICE_NAME = "ProLibertateGames"
|
||||
|
||||
val protocolJson: Json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import java.net.URI
|
||||
* meaning to be had from comparing across them, so nothing does.
|
||||
*
|
||||
* They also carry different applicationIds — a dev build installs as
|
||||
* `…pilgrimsofkatan.dev` — so both can sit on one device, each updating
|
||||
* `…prolibertategames.dev` — so both can sit on one device, each updating
|
||||
* itself down its own line and leaving the other alone.
|
||||
*/
|
||||
enum class UpdateChannel(val label: String, val blurb: String) {
|
||||
|
||||
@@ -126,7 +126,7 @@ class Updater(private val activity: Activity) {
|
||||
(URL(url).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
setRequestProperty("Accept", "application/json")
|
||||
setRequestProperty("User-Agent", "PilgrimsOfKatan-Updater")
|
||||
setRequestProperty("User-Agent", "ProLibertateGames-Updater")
|
||||
connectTimeout = 15_000
|
||||
readTimeout = 15_000
|
||||
}
|
||||
@@ -206,7 +206,7 @@ class Updater(private val activity: Activity) {
|
||||
const val SERVER = "https://git.wallacenet.ca"
|
||||
const val SERVER_HOST = "git.wallacenet.ca"
|
||||
const val OWNER = "devadmin"
|
||||
const val REPO = "PilgrimsOfKatan"
|
||||
const val REPO = "ProLibertateGames"
|
||||
|
||||
/** Release routes, Gitea's `/api/v1` spelling of GitHub's API. */
|
||||
const val API = "$SERVER/api/v1/repos/$OWNER/$REPO"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -22,7 +22,7 @@ class UpdatePolicyTest {
|
||||
tag = tag,
|
||||
versionCode = versionCode,
|
||||
apkUrl = "https://example.invalid/$tag.apk",
|
||||
apkName = "PilgrimsOfKatan-$tag.apk",
|
||||
apkName = "ProLibertateGames-$tag.apk",
|
||||
channel = channel,
|
||||
)
|
||||
|
||||
@@ -60,16 +60,16 @@ class UpdatePolicyTest {
|
||||
// The instance reports its canonical host; the app was told to trust
|
||||
// another. Same bytes, one host, whatever the server names itself.
|
||||
assertEquals(
|
||||
"https://git.wallacenet.ca/devadmin/PilgrimsOfKatan/releases/download/" +
|
||||
"v1.4.1001-dev/PilgrimsOfKatan-v1.4.1001-dev.apk",
|
||||
"https://git.wallacenet.ca/devadmin/ProLibertateGames/releases/download/" +
|
||||
"v1.4.1001-dev/ProLibertateGames-v1.4.1001-dev.apk",
|
||||
onReportedHost(
|
||||
"https://git.saskvalleytech.com/devadmin/PilgrimsOfKatan/releases/download/" +
|
||||
"v1.4.1001-dev/PilgrimsOfKatan-v1.4.1001-dev.apk",
|
||||
"https://git.saskvalleytech.com/devadmin/ProLibertateGames/releases/download/" +
|
||||
"v1.4.1001-dev/ProLibertateGames-v1.4.1001-dev.apk",
|
||||
"git.wallacenet.ca",
|
||||
),
|
||||
)
|
||||
// A URL that already says the right host is left exactly as it is.
|
||||
val already = "https://git.wallacenet.ca/devadmin/PilgrimsOfKatan/releases/download/v1.4.1/a.apk"
|
||||
val already = "https://git.wallacenet.ca/devadmin/ProLibertateGames/releases/download/v1.4.1/a.apk"
|
||||
assertEquals(already, onReportedHost(already, "git.wallacenet.ca"))
|
||||
// Anything unparseable survives untouched rather than being dropped.
|
||||
assertEquals("not a url", onReportedHost("not a url", "git.wallacenet.ca"))
|
||||
|
||||
+18
-13
@@ -1,11 +1,11 @@
|
||||
# CI, releases, and OTA — decisions and how to build
|
||||
|
||||
Status as of the Pilgrims of Katan bootstrap (WALA-118).
|
||||
Status as of the ProLibertateGames bootstrap (WALA-118).
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **The release channel is this instance's own releases.** The app ships from
|
||||
<https://git.wallacenet.ca/devadmin/PilgrimsOfKatan>, and the in-app updater
|
||||
<https://git.wallacenet.ca/devadmin/ProLibertateGames>, and the in-app updater
|
||||
reads that repo's releases over Gitea's REST API. GitHub is not a channel for
|
||||
this project; the only GitHub URLs left are the `upstream` remote and the
|
||||
attribution link in the README.
|
||||
@@ -80,15 +80,15 @@ release — the honest answer for a build nobody stamped.
|
||||
export JAVA_HOME=... # a JDK 17 install
|
||||
export ANDROID_HOME=... # SDK root (or set sdk.dir in local.properties)
|
||||
|
||||
./gradlew assembleDebug # dev-channel app (default: ca.wallacenet.pilgrimsofkatan.dev)
|
||||
PLG_CHANNEL=production ./gradlew assembleDebug # production app (ca.wallacenet.pilgrimsofkatan)
|
||||
./gradlew assembleDebug # dev-channel app (default: ca.wallacenet.prolibertategames.dev)
|
||||
PLG_CHANNEL=production ./gradlew assembleDebug # production app (ca.wallacenet.prolibertategames)
|
||||
./gradlew testDebugUnitTest # JVM unit tests
|
||||
./gradlew lintDebug # Android lint (CI runs this too)
|
||||
```
|
||||
|
||||
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)
|
||||
|
||||
@@ -97,10 +97,15 @@ GITEA_TOKEN=… ./scripts/publish-release.sh v1.4.1001 app/build/outputs/apk/deb
|
||||
```
|
||||
|
||||
The script takes `<tag> <apk> <prerelease|release> [target-sha]`, creates the
|
||||
release through `POST /api/v1/repos/{owner}/{repo}/releases`, and attaches the
|
||||
APK through `POST …/releases/{id}/assets` — which is the upload route Gitea's
|
||||
Swagger declares for this instance (1.27.3). If the tag already exists it
|
||||
publishes into the existing release instead of failing, so a retry is safe.
|
||||
release through `POST /api/v1/repos/{owner}/{repo}/releases`, attaches the APK
|
||||
through `POST …/releases/{id}/assets` — which is the upload route Gitea's
|
||||
Swagger declares for this instance (1.27.3) — and then reads the release back
|
||||
and corrects its state if it did not stick. That last step is not decoration:
|
||||
this instance has been observed to accept `draft: false` at create time and
|
||||
store the release as a draft after the attachment lands, and a draft is
|
||||
invisible to the updater, so the script verifies rather than assumes. If the
|
||||
tag already exists it publishes into the existing release instead of failing,
|
||||
so a retry is safe.
|
||||
|
||||
## What the in-app updater reads
|
||||
|
||||
@@ -108,8 +113,8 @@ publishes into the existing release instead of failing, so a retry is safe.
|
||||
|
||||
| Channel | Endpoint |
|
||||
| --- | --- |
|
||||
| production | `GET /api/v1/repos/devadmin/PilgrimsOfKatan/releases/latest` |
|
||||
| dev | `GET /api/v1/repos/devadmin/PilgrimsOfKatan/releases?limit=30` |
|
||||
| production | `GET /api/v1/repos/devadmin/ProLibertateGames/releases/latest` |
|
||||
| dev | `GET /api/v1/repos/devadmin/ProLibertateGames/releases?limit=30` |
|
||||
|
||||
- `/releases/latest` is documented by Gitea as *the most recent non-prerelease,
|
||||
non-draft release* — the same rule GitHub implements, so the production
|
||||
@@ -121,7 +126,7 @@ publishes into the existing release instead of failing, so a retry is safe.
|
||||
`prerelease`, `draft`, and assets carrying `browser_download_url`. Gitea says
|
||||
`limit` where GitHub says `per_page`.
|
||||
- Each asset must be named so it contains its tag —
|
||||
`PilgrimsOfKatan-v1.4.1001.apk` for tag `v1.4.1001` — because the updater
|
||||
`ProLibertateGames-v1.4.1001.apk` for tag `v1.4.1001` — because the updater
|
||||
picks the versioned APK out of the release's assets by that rule.
|
||||
|
||||
`BuildConfig.OTA_ENABLED` is a kill switch, not a channel decision: it is
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
# Environment:
|
||||
# GITEA_TOKEN required — a token that may create releases on the repo.
|
||||
# GITEA_API API base, default https://git.wallacenet.ca/api/v1
|
||||
# GITEA_REPO owner/name, default devadmin/PilgrimsOfKatan
|
||||
# GITEA_REPO owner/name, default devadmin/ProLibertateGames
|
||||
#
|
||||
# The four arguments can also arrive as RELEASE_TAG, RELEASE_APK,
|
||||
# RELEASE_KIND and RELEASE_TARGET, which is how the workflows pass them.
|
||||
@@ -34,7 +34,7 @@ esac
|
||||
[ -f "$apk" ] || { echo "publish-release: no such APK: ${apk:-<unset>}" >&2; exit 2; }
|
||||
[ -n "${GITEA_TOKEN:-}" ] || { echo "publish-release: GITEA_TOKEN is not set" >&2; exit 2; }
|
||||
|
||||
api="${GITEA_API:-${GITHUB_API_URL:-https://git.wallacenet.ca/api/v1}}/repos/${GITEA_REPO:-${GITHUB_REPOSITORY:-devadmin/PilgrimsOfKatan}}"
|
||||
api="${GITEA_API:-${GITHUB_API_URL:-https://git.wallacenet.ca/api/v1}}/repos/${GITEA_REPO:-${GITHUB_REPOSITORY:-devadmin/ProLibertateGames}}"
|
||||
|
||||
tmp="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||
@@ -43,7 +43,7 @@ trap 'rm -rf "$tmp"' EXIT INT TERM
|
||||
# updater picks the versioned APK out of a release's assets by finding the one
|
||||
# whose name contains the tag, so an asset called app-debug.apk would only be
|
||||
# found by the fallback rule.
|
||||
apk_name="PilgrimsOfKatan-${tag}.apk"
|
||||
apk_name="ProLibertateGames-${tag}.apk"
|
||||
|
||||
# The tag and the file name are both derived from gradle.properties and a run
|
||||
# number, so neither can carry a quote or a backslash into this JSON.
|
||||
@@ -56,7 +56,7 @@ else
|
||||
fi
|
||||
|
||||
cat > "$tmp/release.json" <<JSON
|
||||
{"tag_name":"${tag}","target_commitish":"${target}","name":"Pilgrims of Katan ${tag}","body":"${note}","prerelease":${prerelease},"draft":false}
|
||||
{"tag_name":"${tag}","target_commitish":"${target}","name":"Pro Libertate Games ${tag}","body":"${note}","prerelease":${prerelease},"draft":false}
|
||||
JSON
|
||||
|
||||
code="$(curl -sS -o "$tmp/create.json" -w '%{http_code}' -X POST \
|
||||
@@ -86,6 +86,10 @@ fi
|
||||
id="$(grep -o '"id":[0-9]*' "$tmp/create.json" | head -n1 | tr -dc '0-9' || true)"
|
||||
[ -n "$id" ] || { echo "publish-release: no release id in response" >&2; cat "$tmp/create.json" >&2; echo >&2; exit 1; }
|
||||
|
||||
# Attach the APK first, then check the stored state: uploading has been
|
||||
# observed to leave a freshly created release as a draft, whatever the create
|
||||
# response said, so the check has to be the last thing that happens rather
|
||||
# than a step in the middle.
|
||||
code="$(curl -sS -o "$tmp/asset.json" -w '%{http_code}' -X POST \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-F "attachment=@${apk};filename=${apk_name}" \
|
||||
@@ -98,4 +102,25 @@ if [ "$code" != "201" ] && [ "$code" != "200" ]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Read back what the database actually stored before declaring success. A draft
|
||||
# is invisible to the updater — exactly what a channel must not silently become
|
||||
# — and this instance has been seen to report one as published at create time
|
||||
# and store it as a draft by the time it is read.
|
||||
curl -sS -o "$tmp/stored.json" -H "Authorization: token ${GITEA_TOKEN}" "$api/releases/${id}"
|
||||
if grep -q '"draft":true' "$tmp/stored.json" ||
|
||||
! grep -q "\"prerelease\":${prerelease}" "$tmp/stored.json"; then
|
||||
state="$(curl -sS -o "$tmp/fix.json" -w '%{http_code}' -X PATCH \
|
||||
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"draft\":false,\"prerelease\":${prerelease}}" \
|
||||
"$api/releases/${id}")"
|
||||
if [ "$state" != "200" ]; then
|
||||
echo "publish-release: release ${tag} is stored with the wrong state (HTTP ${state})" >&2
|
||||
cat "$tmp/fix.json" >&2
|
||||
echo >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "publish-release: corrected the stored state of ${tag} (it was saved as a draft)"
|
||||
fi
|
||||
|
||||
echo "publish-release: ${tag} -> release ${id} on ${api#*/repos/}, asset ${apk_name}"
|
||||
|
||||
+1
-1
@@ -14,5 +14,5 @@ dependencyResolutionManagement {
|
||||
}
|
||||
}
|
||||
|
||||
rootProject.name = "PilgrimsOfKatan"
|
||||
rootProject.name = "ProLibertateGames"
|
||||
include(":app")
|
||||
|
||||
Reference in New Issue
Block a user