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

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:
adrianoftyriel
2026-10-14 02:15:30 +00:00
co-authored by Paperclip
parent 05c443a4b5
commit 5c797c8807
22 changed files with 527 additions and 187 deletions
+1 -1
View File
@@ -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>
+60 -12
View File
@@ -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>
+60 -12
View File
@@ -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>
+8 -8
View File
@@ -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 -1
View File
@@ -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>
+1 -1
View File
@@ -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>