Bootstrap Pilgrims of Katan: rebrand, Gitea CI and OTA
Build and Release APK / Build APK and publish release (push) Canceled after 0s
Build and Release APK / Build APK and publish release (push) Canceled after 0s
Import of ProLibertateGames (upstream main, GPL-3.0) rebranded for Wallacenet and repointed at git.wallacenet.ca. - Identity: applicationId ca.wallacenet.pilgrimsofkatan (+ .dev for the dev channel), launcher names "Pilgrims of Katan" / "Pilgrims of Katan DEV", neutral hexagon branding replacing the Wallace tartan. Uncial Antiqua keeps its OFL licence, and the GPL-3.0 LICENSE plus upstream attribution stay. - OTA: Updater.kt now reads this repo's own Gitea releases (/api/v1/repos/devadmin/PilgrimsOfKatan/releases...) and BuildConfig. OTA_ENABLED is on; Gitea answers the same JSON shape the code already parsed. - Build numbers: versionCode = CI run number + plgVersionRunOffset (1000), with the tag and versionName derived from the same figure. The offset is permanent, because Gitea Actions restarts run numbers at 1 and a reset would otherwise make Android reject the next build as a downgrade. - CI: ci.yml publishes a dev prerelease from dev, release.yml a production release from main, both through scripts/publish-release.sh (Gitea REST API, runnable by hand until an Actions runner is registered). - Docs: docs/CI.md records the channel, numbering and runner decisions; docs/README.md seeds the per-game RULES-* conventions; CLAUDE.md carries the agent ground rules. Verified: ./gradlew assembleDebug and testDebugUnitTest green on JDK 17 / compileSdk 34, both variants stamped, installing side by side under the new identity. Co-Authored-By: Paperclip <[email protected]>
This commit is contained in:
co-authored by
Paperclip
parent
d27c2db894
commit
fad4778ad8
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!-- OTA updates from GitHub Releases, plus LAN play over TCP sockets. -->
|
||||
<!-- LAN play over TCP sockets, plus hand-a-down permission for in-app
|
||||
installs — the OTA updater installs releases published on this repo's
|
||||
Gitea; see docs/CI.md. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<!-- Network Service Discovery needs multicast to see hosts on the LAN. -->
|
||||
@@ -23,7 +25,7 @@
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.ProLibertateGames">
|
||||
android:theme="@style/Theme.PilgrimsOfKatan">
|
||||
|
||||
<!-- 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.ProLibertateTheme
|
||||
import org.prolibertate.games.ui.theme.PilgrimsTheme
|
||||
import org.prolibertate.games.update.Updater
|
||||
import java.util.UUID
|
||||
|
||||
@@ -43,7 +43,7 @@ class MainActivity : ComponentActivity() {
|
||||
)
|
||||
|
||||
setContent {
|
||||
ProLibertateTheme {
|
||||
PilgrimsTheme {
|
||||
AppRoot(env)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -308,6 +308,6 @@ class LanTransport(private val context: Context) : Transport {
|
||||
}
|
||||
|
||||
const val CONNECT_TIMEOUT_MS = 8_000
|
||||
const val WIFI_LOCK_TAG = "ProLibertateGames:lan"
|
||||
const val WIFI_LOCK_TAG = "PilgrimsOfKatan: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 = "ProLibertateGames"
|
||||
const val SERVICE_NAME = "PilgrimsOfKatan"
|
||||
|
||||
val protocolJson: Json = Json {
|
||||
ignoreUnknownKeys = true
|
||||
|
||||
@@ -49,7 +49,7 @@ fun LaunchUpdateCheck(env: AppEnv, visible: Boolean) {
|
||||
}
|
||||
|
||||
// Held back behind the splash, so an update prompt cannot appear over the
|
||||
// tartan before the app itself has.
|
||||
// splash before the app itself has.
|
||||
val pending = release
|
||||
if (pending == null || dismissed || !visible) return
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ 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
|
||||
@@ -20,6 +19,7 @@ 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,8 +30,9 @@ 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.TartanBackground
|
||||
import org.prolibertate.games.ui.theme.threadWidthFor
|
||||
import org.prolibertate.games.ui.theme.BrandAccent
|
||||
import org.prolibertate.games.ui.theme.BrandDeep
|
||||
import org.prolibertate.games.ui.theme.BrandField
|
||||
|
||||
/** How long the splash holds at full opacity before fading. */
|
||||
private const val SPLASH_HOLD_MILLIS = 2_000L
|
||||
@@ -45,7 +46,11 @@ private const val SPLASH_FADE_MILLIS = 600
|
||||
val CelticFontFamily = FontFamily(Font(R.font.uncial_antiqua))
|
||||
|
||||
/**
|
||||
* The launch screen: Wallace tartan under the name of the house.
|
||||
* 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.
|
||||
*
|
||||
* 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
|
||||
@@ -70,30 +75,17 @@ fun SplashScreen(
|
||||
onFinished()
|
||||
}
|
||||
|
||||
BoxWithConstraints(modifier = modifier.fillMaxSize().alpha(alpha)) {
|
||||
BoxWithConstraints(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.alpha(alpha)
|
||||
.background(Brush.verticalGradient(listOf(BrandField, BrandDeep))),
|
||||
) {
|
||||
// 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
|
||||
@@ -103,7 +95,7 @@ fun SplashScreen(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = "Pro Libertate",
|
||||
text = "Pilgrims",
|
||||
style = TextStyle(
|
||||
fontFamily = CelticFontFamily,
|
||||
fontSize = titleSize,
|
||||
@@ -117,11 +109,11 @@ fun SplashScreen(
|
||||
),
|
||||
)
|
||||
Text(
|
||||
text = "Games",
|
||||
text = "of Katan",
|
||||
style = TextStyle(
|
||||
fontFamily = CelticFontFamily,
|
||||
fontSize = titleSize,
|
||||
color = Color(0xFFF2C200),
|
||||
color = BrandAccent,
|
||||
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.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
|
||||
/**
|
||||
* 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, WallaceGold),
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, AccentGold),
|
||||
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.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
|
||||
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, WallaceGold),
|
||||
border = BorderStroke(if (highlighted) 3.dp else 0.dp, AccentGold),
|
||||
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.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.WallaceRed
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.AccentRed
|
||||
|
||||
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 -> WallaceGold
|
||||
isDestination -> WallaceGold
|
||||
selected -> AccentGold
|
||||
isDestination -> AccentGold
|
||||
else -> Parchment.copy(alpha = 0.35f)
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(diameter)
|
||||
.background(
|
||||
color = if (filled) WallaceRed else FeltGreenDark,
|
||||
color = if (filled) AccentRed 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.WallaceGold
|
||||
import org.prolibertate.games.ui.theme.WallaceRed
|
||||
import org.prolibertate.games.ui.theme.AccentGold
|
||||
import org.prolibertate.games.ui.theme.AccentRed
|
||||
|
||||
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) WallaceGold else WallaceRed.copy(alpha = 0.4f),
|
||||
color = if (held) AccentGold else AccentRed.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 = WallaceRed,
|
||||
color = AccentRed,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -263,7 +263,7 @@ private fun CardRow(
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = if (written == null && open) WallaceRed else MaterialTheme.colorScheme.onSurface,
|
||||
color = if (written == null && open) AccentRed else MaterialTheme.colorScheme.onSurface,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,187 +0,0 @@
|
||||
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,6 +6,7 @@ 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)
|
||||
@@ -14,6 +15,31 @@ 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
|
||||
@@ -39,7 +65,7 @@ private val LightColours = lightColorScheme(
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun ProLibertateTheme(
|
||||
fun PilgrimsTheme(
|
||||
darkTheme: Boolean = isSystemInDarkTheme(),
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
|
||||
@@ -5,13 +5,13 @@ package org.prolibertate.games.update
|
||||
*
|
||||
* This is a property of the build, never a preference. The two channels are
|
||||
* published by different workflows and have entirely separate version
|
||||
* sequences — a dev build's versionCode is its CI run number, a production
|
||||
* build's is its release run number, and the two are unrelated. There is no
|
||||
* sequences — a dev build's versionCode is its CI build number, a production
|
||||
* build's is its release build number, and the two are unrelated. There is no
|
||||
* meaning to be had from comparing across them, so nothing does.
|
||||
*
|
||||
* They also carry different applicationIds — a dev build installs as
|
||||
* `…games.dev` — so both can sit on one device, each updating itself down its
|
||||
* own line and leaving the other alone.
|
||||
* `…pilgrimsofkatan.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) {
|
||||
PRODUCTION("Production", "Stable builds released from main"),
|
||||
@@ -21,7 +21,7 @@ enum class UpdateChannel(val label: String, val blurb: String) {
|
||||
/** Marks a tag or a version name as belonging to the dev channel. */
|
||||
const val DEV_SUFFIX = "-dev"
|
||||
|
||||
/** One published build, as read off a GitHub release. */
|
||||
/** One published build, as read off a repository release. */
|
||||
data class Release(
|
||||
val tag: String,
|
||||
val versionCode: Int,
|
||||
@@ -49,14 +49,14 @@ fun channelOfVersionName(versionName: String): UpdateChannel =
|
||||
* The channel a release belongs to.
|
||||
*
|
||||
* Two independent marks, and either is enough: the `-dev` suffix the workflow
|
||||
* puts on the tag, and GitHub's own prerelease flag. A release would have to
|
||||
* lose both to be mistaken for the other channel.
|
||||
* puts on the tag, and the release's own prerelease flag. A release would have
|
||||
* to lose both to be mistaken for the other channel.
|
||||
*/
|
||||
fun channelOfTag(tag: String, prerelease: Boolean): UpdateChannel =
|
||||
if (tag.endsWith(DEV_SUFFIX) || prerelease) UpdateChannel.DEV else UpdateChannel.PRODUCTION
|
||||
|
||||
/**
|
||||
* The run number out of a `v<series>.<n>` tag, which is what an APK's
|
||||
* The build number out of a `v<series>.<n>` tag, which is what an APK's
|
||||
* versionCode is.
|
||||
*
|
||||
* Only the trailing number is read. The series is a name for people, and
|
||||
@@ -70,10 +70,10 @@ fun versionCodeOfTag(tag: String): Int? =
|
||||
* nothing at all.
|
||||
*
|
||||
* Only an actual update is worth a word. Opening the app is not asking a
|
||||
* question, so a check that finds nothing — or that cannot reach GitHub at all
|
||||
* — stays quiet; a phone on a train would otherwise be told off for it every
|
||||
* single launch. The button in Settings *is* asking a question, and reports
|
||||
* every answer including those two.
|
||||
* question, so a check that finds nothing — or that cannot reach the release
|
||||
* server at all — stays quiet; a phone on a train would otherwise be told off
|
||||
* for it every single launch. The button in Settings *is* asking a question, and
|
||||
* reports every answer including those two.
|
||||
*/
|
||||
fun launchOffer(verdict: UpdateVerdict): Release? =
|
||||
(verdict as? UpdateVerdict.Install)?.release
|
||||
|
||||
@@ -10,17 +10,34 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.prolibertate.games.BuildConfig
|
||||
import java.io.File
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
/**
|
||||
* Over-the-air updates from the repository's GitHub Releases.
|
||||
* Over-the-air updates from the repository's releases.
|
||||
*
|
||||
* Release tags are v<series>.<run-number> and the APK's versionCode is that same run
|
||||
* number, so comparing the two is enough to tell whether a newer build exists.
|
||||
* The repository's releases must be publicly downloadable — no token is
|
||||
* embedded in the app, and GitHub blocks anonymous access to private assets.
|
||||
* The channel is the Gitea instance this app ships from
|
||||
* (`git.wallacenet.ca`), not GitHub. Gitea's release API answers with the
|
||||
* same shape the updater reads — `tag_name`, `prerelease`, `draft`, and
|
||||
* assets carrying `browser_download_url` — so this is the same code the
|
||||
* upstream ran, pointed at `/api/v1/repos/...` routes instead of
|
||||
* `api.github.com`, with `limit` where GitHub says `per_page`.
|
||||
*
|
||||
* Release tags are v<series>.<n>, where **n is the stamped build number the
|
||||
* APK's versionCode was built from** (the CI run number plus the fixed
|
||||
* `plgVersionRunOffset` — see docs/CI.md), so comparing the two is enough to
|
||||
* tell whether a newer build exists.
|
||||
*
|
||||
* The repository and its releases are public: no token is embedded in the
|
||||
* app, and every download is anonymous by design. If the repo were ever made
|
||||
* private, every check would simply start failing rather than leaking
|
||||
* anything.
|
||||
*
|
||||
* `BuildConfig.OTA_ENABLED` is a kill switch, not a channel decision — it is
|
||||
* true in this build; setting it false makes every check report that and
|
||||
* fetch nothing.
|
||||
*
|
||||
* There is nothing to choose here. The channel comes from the running APK, so
|
||||
* a dev install looks only at dev builds and a production install only at
|
||||
@@ -35,6 +52,11 @@ class Updater(private val activity: Activity) {
|
||||
* this phone is on, and the phone already knows.
|
||||
*/
|
||||
suspend fun check(): UpdateVerdict = withContext(Dispatchers.IO) {
|
||||
if (!BuildConfig.OTA_ENABLED) {
|
||||
return@withContext UpdateVerdict.Refused(
|
||||
"In-app updates are not enabled for this build yet."
|
||||
)
|
||||
}
|
||||
try {
|
||||
val channel = installedChannel()
|
||||
verdictFor(
|
||||
@@ -48,23 +70,28 @@ class Updater(private val activity: Activity) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Production reads GitHub's "latest release", which by definition skips
|
||||
* prereleases. Dev walks the release list for the newest prerelease, which
|
||||
* is what the CI pipeline publishes from the dev branch.
|
||||
* Production reads the "latest release", which by definition skips
|
||||
* prereleases — Gitea implements it as the most recent non-prerelease,
|
||||
* non-draft release, so the rule holds on this instance too.
|
||||
*
|
||||
* Dev walks the release list for the newest prerelease, which is what the
|
||||
* CI pipeline publishes from the dev branch. The list is taken as the
|
||||
* highest versionCode among the dev releases rather than the first one
|
||||
* returned, so the answer does not depend on how the server chose to
|
||||
* order the page.
|
||||
*/
|
||||
private fun fetchLatestRelease(channel: UpdateChannel): Release? = when (channel) {
|
||||
UpdateChannel.PRODUCTION ->
|
||||
getJsonObject("https://api.github.com/repos/$OWNER/$REPO/releases/latest")
|
||||
getJsonObject("$API/releases/latest")
|
||||
?.let { toRelease(it) }
|
||||
|
||||
UpdateChannel.DEV ->
|
||||
getJsonArray("https://api.github.com/repos/$OWNER/$REPO/releases?per_page=30")
|
||||
getJsonArray("$API/releases?limit=30")
|
||||
?.let { releases ->
|
||||
// GitHub returns newest first.
|
||||
(0 until releases.length())
|
||||
.map { releases.getJSONObject(it) }
|
||||
.firstOrNull { it.optBoolean("prerelease") && !it.optBoolean("draft") }
|
||||
?.let { toRelease(it) }
|
||||
.mapNotNull { toRelease(releases.getJSONObject(it)) }
|
||||
.filter { it.channel == UpdateChannel.DEV }
|
||||
.maxByOrNull { it.versionCode }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,8 +125,8 @@ class Updater(private val activity: Activity) {
|
||||
private fun openConnection(url: String): HttpURLConnection =
|
||||
(URL(url).openConnection() as HttpURLConnection).apply {
|
||||
requestMethod = "GET"
|
||||
setRequestProperty("Accept", "application/vnd.github+json")
|
||||
setRequestProperty("User-Agent", "ProLibertateGames-Updater")
|
||||
setRequestProperty("Accept", "application/json")
|
||||
setRequestProperty("User-Agent", "PilgrimsOfKatan-Updater")
|
||||
connectTimeout = 15_000
|
||||
readTimeout = 15_000
|
||||
}
|
||||
@@ -175,7 +202,12 @@ class Updater(private val activity: Activity) {
|
||||
activity.packageManager.getPackageInfo(activity.packageName, 0).versionName ?: "unknown"
|
||||
|
||||
private companion object {
|
||||
const val OWNER = "adrianoftyriel"
|
||||
const val REPO = "ProLibertateGames"
|
||||
/** The Gitea instance that publishes this app's releases. */
|
||||
const val SERVER = "https://git.wallacenet.ca"
|
||||
const val OWNER = "devadmin"
|
||||
const val REPO = "PilgrimsOfKatan"
|
||||
|
||||
/** Release routes, Gitea's `/api/v1` spelling of GitHub's API. */
|
||||
const val API = "$SERVER/api/v1/repos/$OWNER/$REPO"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,70 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<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>
|
||||
<path
|
||||
android:fillColor="@color/brand_field"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
<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="#00000000"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
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" />
|
||||
</vector>
|
||||
|
||||
@@ -1,70 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A single square of the Wallace tartan, set on the bias.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<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>
|
||||
<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" />
|
||||
</vector>
|
||||
|
||||
@@ -1,70 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
A single square of the Wallace tartan, set on the bias.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="48dp"
|
||||
android:height="48dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<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>
|
||||
<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" />
|
||||
</vector>
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
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.
|
||||
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.
|
||||
-->
|
||||
<resources>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</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>
|
||||
<!-- 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>
|
||||
</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 "Pro Libertate Games DEV"
|
||||
differs by channel: a dev build installs as "Pilgrims of Katan 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.ProLibertateGames" parent="android:Theme.Material.NoActionBar">
|
||||
<style name="Theme.PilgrimsOfKatan" 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>
|
||||
|
||||
Reference in New Issue
Block a user