docs: plan icônes launcher et correction zone sûre circulaire
This commit is contained in:
2 files changed
+247
-2
No files matched your search
@@ -0,0 +1,245 @@
|
||||
# Icônes launcher depuis le logo — Plan d'implémentation
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Décliner `docs/branding/logo.svg` en icônes launcher Android (adaptive + legacy), iOS (appiconset) et desktop, via `docs/branding/generate-icons.sh`.
|
||||
|
||||
**Architecture:** 3 variantes SVG committées (square / foreground / monochrome) → script bash idempotent avec `rsvg-convert` (+ `python3` pour les tailles iOS lues dans `Contents.json`, + `magick` pour le background uni) qui écrase les PNG existants in situ.
|
||||
|
||||
**Tech Stack:** SVG 1.1, bash, rsvg-convert 2.62, python3, ImageMagick, Compose Multiplatform (`painterResource`).
|
||||
|
||||
## Global Constraints
|
||||
|
||||
- Palette uniquement : `#1D3557` `#457B9D` `#A8DADC` `#E63946` `#F1FAEE` (+ `#FFFFFF` réservé à la couche monochrome)
|
||||
- Aucun `<text>` dans les SVG ; aucune référence externe
|
||||
- Zone sûre Android : cercle de 66 dp de diamètre centré dans le canvas 108 (scale 0.5073)
|
||||
- `Contents.json` iOS non modifié ; chemins/noms des PNG Android inchangés
|
||||
- Géométrie tuiles/glyphes : verbatim du master `logo.svg` (spec 2026-08-31-logo-design §3)
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Variantes SVG sources
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/branding/icon-square.svg`
|
||||
- Create: `docs/branding/icon-foreground.svg`
|
||||
- Create: `docs/branding/icon-monochrome.svg`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: géométrie du master `logo.svg`
|
||||
- Produces: 3 SVG sources consommés par `generate-icons.sh` (Task 2)
|
||||
|
||||
- [ ] **Step 1: Créer `icon-square.svg`** (plein cadre, sans arrondi — pour iOS)
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 128 128" role="img" aria-labelledby="title">
|
||||
<title id="title">Livrets — icône plein cadre</title>
|
||||
<rect x="0" y="0" width="128" height="128" fill="#1D3557"/>
|
||||
<rect x="18" y="18" width="42" height="42" rx="12" fill="#A8DADC"/>
|
||||
<rect x="68" y="18" width="42" height="42" rx="12" fill="#E63946"/>
|
||||
<rect x="18" y="68" width="42" height="42" rx="12" fill="#F1FAEE"/>
|
||||
<rect x="68" y="68" width="42" height="42" rx="12" fill="#457B9D"/>
|
||||
<path d="M29,28 L49,28 L36,50" fill="none" stroke="#1D3557" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M81,31 L97,47 M97,31 L81,47" fill="none" stroke="#F1FAEE" stroke-width="6.5" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="82.75" r="5.5" fill="#1D3557"/>
|
||||
<circle cx="39" cy="93.25" r="7.5" fill="#1D3557"/>
|
||||
<path d="M81,81 C81,73 97,73 97,81 C97,86.5 89,86.5 89,93 L89,95.5" fill="none" stroke="#F1FAEE" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="89" cy="102.5" r="3.4" fill="#F1FAEE"/>
|
||||
</svg>
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Créer `icon-foreground.svg`** (viewBox 108, scale 0.5073 vers centre 54,54)
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 108 108" role="img" aria-labelledby="title">
|
||||
<title id="title">Livrets — couche foreground adaptive</title>
|
||||
<g transform="translate(54,54) scale(0.5073) translate(-64,-64)">
|
||||
<rect x="18" y="18" width="42" height="42" rx="12" fill="#A8DADC"/>
|
||||
<rect x="68" y="18" width="42" height="42" rx="12" fill="#E63946"/>
|
||||
<rect x="18" y="68" width="42" height="42" rx="12" fill="#F1FAEE"/>
|
||||
<rect x="68" y="68" width="42" height="42" rx="12" fill="#457B9D"/>
|
||||
<path d="M29,28 L49,28 L36,50" fill="none" stroke="#1D3557" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M81,31 L97,47 M97,31 L81,47" fill="none" stroke="#F1FAEE" stroke-width="6.5" stroke-linecap="round"/>
|
||||
<circle cx="39" cy="82.75" r="5.5" fill="#1D3557"/>
|
||||
<circle cx="39" cy="93.25" r="7.5" fill="#1D3557"/>
|
||||
<path d="M81,81 C81,73 97,73 97,81 C97,86.5 89,86.5 89,93 L89,95.5" fill="none" stroke="#F1FAEE" stroke-width="6" stroke-linecap="round"/>
|
||||
<circle cx="89" cy="102.5" r="3.4" fill="#F1FAEE"/>
|
||||
</g>
|
||||
</svg>
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Créer `icon-monochrome.svg`** (tuiles blanches uniquement, même groupe)
|
||||
|
||||
```svg
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" viewBox="0 0 108 108" role="img" aria-labelledby="title">
|
||||
<title id="title">Livrets — couche monochrome adaptive</title>
|
||||
<g transform="translate(54,54) scale(0.5073) translate(-64,-64)">
|
||||
<rect x="18" y="18" width="42" height="42" rx="12" fill="#FFFFFF"/>
|
||||
<rect x="68" y="18" width="42" height="42" rx="12" fill="#FFFFFF"/>
|
||||
<rect x="18" y="68" width="42" height="42" rx="12" fill="#FFFFFF"/>
|
||||
<rect x="68" y="68" width="42" height="42" rx="12" fill="#FFFFFF"/>
|
||||
</g>
|
||||
</svg>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Valider**
|
||||
|
||||
```bash
|
||||
for f in docs/branding/icon-square.svg docs/branding/icon-foreground.svg docs/branding/icon-monochrome.svg; do
|
||||
xmllint --noout "$f" && grep -c "<text" "$f" | grep -q "^0$"
|
||||
done
|
||||
# attendu : aucune erreur, aucune sortie
|
||||
grep -oE "#[0-9A-Fa-f]{6}" docs/branding/icon-monochrome.svg | sort -u # attendu : #FFFFFF uniquement
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add docs/branding/icon-square.svg docs/branding/icon-foreground.svg docs/branding/icon-monochrome.svg
|
||||
git commit -m "feat: variantes svg des icônes (square, foreground, monochrome)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Script `generate-icons.sh` et génération des PNG
|
||||
|
||||
**Files:**
|
||||
- Create: `docs/branding/generate-icons.sh`
|
||||
- Modify (généré): `androidApp/src/main/res/mipmap-*/ic_launcher*.png`
|
||||
- Modify (généré): `iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/*.png`
|
||||
- Create: `desktopApp/src/main/resources/icons/icon.png`
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: les 4 SVG de `docs/branding/` (logo, square, foreground, monochrome)
|
||||
- Produces: tous les PNG launcher + icône desktop ; script réutilisable après retouche du logo
|
||||
|
||||
- [ ] **Step 1: Créer le script**
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Génère les icônes launcher (Android, iOS, desktop) depuis les SVG de docs/branding/.
|
||||
# Prérequis : rsvg-convert, magick, python3. Idempotent.
|
||||
set -euo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
BRANDING="$ROOT/docs/branding"
|
||||
RES="$ROOT/androidApp/src/main/res"
|
||||
IOS_APPSET="$ROOT/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset"
|
||||
|
||||
for tool in rsvg-convert magick python3; do
|
||||
command -v "$tool" >/dev/null 2>&1 || { echo "Erreur : '$tool' requis" >&2; exit 1; }
|
||||
done
|
||||
|
||||
# --- Android : legacy (carte arrondie du master) ---
|
||||
for pair in mdpi:48 hdpi:72 xhdpi:96 xxhdpi:144 xxxhdpi:192; do
|
||||
dpi="${pair%%:*}"; size="${pair##*:}"
|
||||
rsvg-convert -w "$size" -h "$size" "$BRANDING/logo.svg" -o "$RES/mipmap-$dpi/ic_launcher.png"
|
||||
done
|
||||
|
||||
# --- Android : couches adaptive (108 dp) ---
|
||||
for pair in mdpi:108 hdpi:162 xhdpi:216 xxhdpi:324 xxxhdpi:432; do
|
||||
dpi="${pair%%:*}"; size="${pair##*:}"
|
||||
magick -size "${size}x${size}" xc:'#1D3557' "$RES/mipmap-$dpi/ic_launcher_background.png"
|
||||
rsvg-convert -w "$size" -h "$size" "$BRANDING/icon-foreground.svg" -o "$RES/mipmap-$dpi/ic_launcher_foreground.png"
|
||||
rsvg-convert -w "$size" -h "$size" "$BRANDING/icon-monochrome.svg" -o "$RES/mipmap-$dpi/ic_launcher_monochrome.png"
|
||||
done
|
||||
|
||||
# --- iOS : tailles lues dans Contents.json ---
|
||||
python3 - "$BRANDING/icon-square.svg" "$IOS_APPSET" <<'PY'
|
||||
import json, pathlib, subprocess, sys
|
||||
|
||||
svg, appset = sys.argv[1], pathlib.Path(sys.argv[2])
|
||||
for img in json.loads((appset / "Contents.json").read_text())["images"]:
|
||||
w, _ = (float(v) for v in img["size"].split("x"))
|
||||
px = round(w * float(img["scale"].rstrip("x")))
|
||||
subprocess.run(
|
||||
["rsvg-convert", "-w", str(px), "-h", str(px), svg, "-o", str(appset / img["filename"])],
|
||||
check=True,
|
||||
)
|
||||
PY
|
||||
|
||||
# --- Desktop ---
|
||||
mkdir -p "$ROOT/desktopApp/src/main/resources/icons"
|
||||
rsvg-convert -w 512 -h 512 "$BRANDING/logo.svg" -o "$ROOT/desktopApp/src/main/resources/icons/icon.png"
|
||||
|
||||
echo "Icônes générées ✓"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Rendre exécutable et lancer**
|
||||
|
||||
```bash
|
||||
chmod +x docs/branding/generate-icons.sh
|
||||
./docs/branding/generate-icons.sh
|
||||
# attendu : « Icônes générées ✓ » sans erreur
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Vérifier les dimensions générées**
|
||||
|
||||
```bash
|
||||
sips -g pixelWidth androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher.png | grep -q 192 # legacy
|
||||
sips -g pixelWidth androidApp/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png | grep -q 432
|
||||
sips -g pixelWidth iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon~ios-marketing.png | grep -q 1024
|
||||
sips -g pixelWidth desktopApp/src/main/resources/icons/icon.png | grep -q 512
|
||||
# attendu : les 4 grep réussissent (silence = OK)
|
||||
for f in iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/*.png; do sips -g pixelWidth "$f" | grep -qE "[1-9][0-9]*"; done
|
||||
# attendu : tous les PNG iOS existent et non vides
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Contrôle visuel companion** (grille des icônes + simulation masque rond Android) puis commit
|
||||
|
||||
```bash
|
||||
git add androidApp/src/main/res docs/branding/generate-icons.sh desktopApp/src/main/resources iosApp/iosApp/Assets.xcassets
|
||||
git commit -m "feat: générer les icônes launcher depuis le logo (script + assets)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Icône de fenêtre desktop
|
||||
|
||||
**Files:**
|
||||
- Modify: `desktopApp/src/main/kotlin/com/livrets/desktop/Main.kt:3-6` (imports) et `:10-14` (Window)
|
||||
|
||||
**Interfaces:**
|
||||
- Consumes: `desktopApp/src/main/resources/icons/icon.png` (Task 2)
|
||||
- Produces: fenêtre desktop icônée
|
||||
|
||||
- [ ] **Step 1: Ajouter l'import et le paramètre icon**
|
||||
|
||||
```kotlin
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.application
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import com.livrets.App
|
||||
import org.jetbrains.compose.ui.res.painterResource
|
||||
|
||||
fun main() = application {
|
||||
Window(
|
||||
onCloseRequest = ::exitApplication,
|
||||
title = "Livrets",
|
||||
state = rememberWindowState(width = 480.dp, height = 800.dp),
|
||||
icon = painterResource("icons/icon.png"),
|
||||
) {
|
||||
App(filesDirPath = storageDir())
|
||||
}
|
||||
}
|
||||
|
||||
private fun storageDir(): String =
|
||||
System.getProperty("user.home") + "/.livrets"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Compiler et tester**
|
||||
|
||||
```bash
|
||||
./gradlew :desktopApp:compileKotlin --console=plain 2>&1 | tail -3
|
||||
# attendu : BUILD SUCCESSFUL (sinon ajuster l'import painterResource selon la version compose)
|
||||
./gradlew :sharedUI:jvmTest :androidApp:assembleDebug --console=plain 2>&1 | tail -3
|
||||
# attendu : BUILD SUCCESSFUL
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add desktopApp/src/main/kotlin/com/livrets/desktop/Main.kt
|
||||
git commit -m "feat: icône de fenêtre desktop"
|
||||
```
|
||||
Reference in new issue
Block a user