feat(android): a shield instead of a padlock, and the launch screen matches

The padlock was drawn in thin blue strokes on slate: at 36 px the outline muddied and the shackle vanished. A filled white shield with a punched keyhole keeps its silhouette at every size, and says protection more plainly.

The keyhole is CUT OUT rather than painted, through an even-odd fill on a single path - painting it meant keeping two files agreeing on one colour, and any drift would have read as a smudge. The launch layer is the exception: it sits flat on the system background, where a cut-out would be a hole, so there the keyhole is painted and stays entirely inside the white shield.

The launch background takes the top of the app gradient instead of slate, so the launch screen dissolves into the first screen rather than cutting against it. No gradient inside the vectors: the aapt this chain uses does not know inline gradients.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
r-zakarya
2026-08-17 00:02:27 +01:00
parent 8c1cabb5bd
commit ec8b5b3410
12 changed files with 78 additions and 74 deletions
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 655 B

After

Width:  |  Height:  |  Size: 711 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 877 B

After

Width:  |  Height:  |  Size: 922 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

@@ -1,13 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Fond de l'icone adaptative. Le systeme y applique son propre masque
(rond, carre arrondi, goutte selon le lanceur), donc il doit couvrir les
108 unites entieres : tout ce qui manque devient un bord blanc. -->
<!-- Fond de l'icone adaptative : un aplat, dans le bleu de l'application.
Le systeme le decoupe a la forme voulue par le lanceur (cercle, carre
arrondi, goutte), donc il doit couvrir les 108 unites entieres.
Pas de degrade : aapt - la premiere version, celle que sert la chaine de
ce projet - ne connait pas les degrades en ligne dans un vecteur. -->
<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="#0F172A"
android:pathData="M0,0h108v108h-108z" />
android:fillColor="#1B3563"
android:pathData="M0,0 L108,0 L108,108 L0,108 Z" />
</vector>
@@ -1,29 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Avant-plan de l'icone adaptative : un cadenas.
<!-- Avant-plan de l'icone adaptative : un bouclier a serrure.
TOUT DOIT TENIR DANS LES 66 UNITES CENTRALES (soit 21..87) : le systeme
recadre et anime la couche autour de cette zone sure, et ce qui deborde
peut etre rogne selon le lanceur. Le dessin ci-dessous occupe 34..74. -->
peut etre rogne selon le lanceur. Le dessin occupe 31..77 en x, 29..82
en y.
UN SEUL CHEMIN, EN EVENODD. La serrure n'est pas peinte de la couleur du
fond, elle est EVIDEE : le fond se voit au travers. Peinte, elle imposerait
de tenir deux fichiers d'accord sur une couleur, et le moindre ecart se
verrait comme une tache. Evidee, il n'y a rien a synchroniser.
Blanc plein plutot qu'un trait bleu : a 36 px un contour se boue, une
silhouette pleine tient. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<!-- Anse : trait, pas remplissage, pour garder l'interieur ajoure. -->
<path
android:strokeColor="#3B82F6"
android:strokeWidth="5.5"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M44,56 L44,47 A10,10 0 0 1 64,47 L64,56" />
<!-- Corps -->
<path
android:fillColor="#3B82F6"
android:pathData="M38,54 L70,54 A4,4 0 0 1 74,58 L74,76 A4,4 0 0 1 70,80 L38,80 A4,4 0 0 1 34,76 L34,58 A4,4 0 0 1 38,54 Z" />
<!-- Serrure, evidee dans la couleur du fond -->
<path
android:fillColor="#0F172A"
android:pathData="M54,62 m-4,0 a4,4 0 1,0 8,0 a4,4 0 1,0 -8,0 Z" />
<path
android:fillColor="#0F172A"
android:pathData="M52.2,66 L55.8,66 L55,73.5 L53,73.5 Z" />
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="M54,29 L77,37 L77,56 C77,69.5 67,78 54,82 C41,78 31,69.5 31,56 L31,37 Z
M54,50 m-7,0 a7,7 0 1,0 14,0 a7,7 0 1,0 -14,0 Z
M51,55.5 L57,55.5 L58.6,70 L49.4,70 Z" />
</vector>
@@ -1,22 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Version monochrome, pour les icones themees d'Android 13+. Le systeme
ignore les couleurs et ne garde que la silhouette, qu'il teinte selon le
fond d'ecran. La serrure doit donc etre un TROU dans le trace, pas une
forme d'une autre couleur - d'ou le remplissage evenOdd plutot que deux
chemins superposes. -->
<!-- Couche monochrome (icones thematiques d'Android 13+). Le systeme ne
retient que la FORME : il repeint le dessin avec la couleur du theme de
l'utilisateur, donc la couleur posee ici n'a aucune importance.
Meme bouclier que l'avant-plan, serrure evidee de la meme facon. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:strokeColor="#FFFFFF"
android:strokeWidth="5.5"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M44,56 L44,47 A10,10 0 0 1 64,47 L64,56" />
<path
android:fillColor="#FFFFFF"
android:fillType="evenOdd"
android:pathData="M38,54 L70,54 A4,4 0 0 1 74,58 L74,76 A4,4 0 0 1 70,80 L38,80 A4,4 0 0 1 34,76 L34,58 A4,4 0 0 1 38,54 Z M54,62 m-4,0 a4,4 0 1,0 8,0 a4,4 0 1,0 -8,0 Z M52.2,66 L55.8,66 L55,73.5 L53,73.5 Z" />
android:pathData="M54,29 L77,37 L77,56 C77,69.5 67,78 54,82 C41,78 31,69.5 31,56 L31,37 Z
M54,50 m-7,0 a7,7 0 1,0 14,0 a7,7 0 1,0 -14,0 Z
M51,55.5 L57,55.5 L58.6,70 L49.4,70 Z" />
</vector>
@@ -2,26 +2,25 @@
<!-- Icone de l'ecran de demarrage.
Sur Android 12+, le systeme l'affiche via windowSplashScreenAnimatedIcon
dans un disque de 288 unites dont il ne garantit que les 192 centrales :
le dessin ci-dessous occupe 101..187, bien a l'interieur.
C'est le meme cadenas que l'icone du lanceur, a l'echelle. -->
le dessin occupe 103..186, bien a l'interieur.
Meme bouclier que l'icone du lanceur, a l'echelle.
ICI LA SERRURE EST PEINTE, pas evidee : la couche de demarrage est posee
a plat sur le fond du systeme, et un evidement y ferait un trou. Elle est
entierement contenue dans le bouclier blanc, donc sa couleur n'a qu'a
trancher sur du blanc - elle ne depend d'aucun autre fichier. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="288dp"
android:height="288dp"
android:viewportWidth="288"
android:viewportHeight="288">
<path
android:strokeColor="#3B82F6"
android:strokeWidth="11"
android:strokeLineCap="round"
android:fillColor="#00000000"
android:pathData="M124,139 L124,121 A20,20 0 0 1 164,121 L164,139" />
android:fillColor="#FFFFFF"
android:pathData="M144,96.3 L185.4,110.7 L185.4,144.9 C185.4,169.2 167.4,184.5 144,191.7 C120.6,184.5 102.6,169.2 102.6,144.9 L102.6,110.7 Z" />
<path
android:fillColor="#3B82F6"
android:pathData="M112,135 L176,135 A8,8 0 0 1 184,143 L184,179 A8,8 0 0 1 176,187 L112,187 A8,8 0 0 1 104,179 L104,143 A8,8 0 0 1 112,135 Z" />
android:fillColor="#1B3563"
android:pathData="M144,134.1 m-12.6,0 a12.6,12.6 0 1,0 25.2,0 a12.6,12.6 0 1,0 -25.2,0 Z" />
<path
android:fillColor="#0F172A"
android:pathData="M144,151 m-8,0 a8,8 0 1,0 16,0 a8,8 0 1,0 -16,0 Z" />
<path
android:fillColor="#0F172A"
android:pathData="M140.4,159 L151.6,159 L149,174 L143,174 Z" />
android:fillColor="#1B3563"
android:pathData="M138.6,144 L149.4,144 L152.3,170.1 L135.7,170.1 Z" />
</vector>
+3 -1
View File
@@ -3,5 +3,7 @@
ecran de demarrage blanc suivi d'une application sombre donne un flash
desagreable a chaque lancement. -->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<color name="splash_background">#0F172A</color>
<!-- Le haut du degrade de l'application : l'ecran de demarrage se fond
alors dans le premier ecran au lieu de le trancher. -->
<color name="splash_background">#3E6394</color>
</resources>
+31 -23
View File
@@ -1,6 +1,6 @@
# Genere les icones PNG du lanceur, pour Android < 8 qui ignore les icones
# adaptatives. Meme dessin que les vecteurs de android-res\drawable :
# cadenas cyan sur ardoise.
# bouclier blanc a serrure, sur le bleu de l'application.
#
# A relancer seulement si le dessin change ; les PNG sont suivis par git.
@@ -8,8 +8,8 @@ $ErrorActionPreference = 'Stop'
Add-Type -AssemblyName System.Drawing
$root = Join-Path $PSScriptRoot 'android-res'
$bg = [System.Drawing.Color]::FromArgb(255, 15, 23, 42) # #0F172A
$fg = [System.Drawing.Color]::FromArgb(255, 59, 130, 246) # #3B82F6
$bg = [System.Drawing.Color]::FromArgb(255, 27, 53, 99) # #1B3563
$fg = [System.Drawing.Color]::FromArgb(255, 255, 255, 255) # blanc
# densite -> cote en pixels, tel qu'Android les attend
$sizes = @{ 'drawable-ldpi' = 36; 'drawable-mdpi' = 48; 'drawable-hdpi' = 72
@@ -38,29 +38,37 @@ foreach ($dir in $sizes.Keys) {
# Echelle : le dessin vectoriel vit dans un carre de 108.
$u = $n / 108.0
$brush = New-Object System.Drawing.SolidBrush $fg
$pen = New-Object System.Drawing.Pen $fg, ([float](5.5 * $u))
$pen.StartCap = 'Round'; $pen.EndCap = 'Round'
# Anse : demi-cercle ouvert vers le bas, puis deux montants.
$g.DrawArc($pen, [float](44*$u), [float](37*$u), [float](20*$u), [float](20*$u), 180, 180)
$g.DrawLine($pen, [float](44*$u), [float](47*$u), [float](44*$u), [float](56*$u))
$g.DrawLine($pen, [float](64*$u), [float](47*$u), [float](64*$u), [float](56*$u))
# Bouclier : sommet, epaules, montants, puis les deux courbes qui se
# rejoignent en pointe. Memes coordonnees que ic_launcher_foreground.xml.
$s = New-Object System.Drawing.Drawing2D.GraphicsPath
$s.AddLines(@(
(New-Object System.Drawing.PointF ([float](54*$u), [float](29*$u))),
(New-Object System.Drawing.PointF ([float](77*$u), [float](37*$u))),
(New-Object System.Drawing.PointF ([float](77*$u), [float](56*$u)))))
$s.AddBezier(
[float](77*$u), [float](56*$u), [float](77*$u), [float](69.5*$u),
[float](67*$u), [float](78*$u), [float](54*$u), [float](82*$u))
$s.AddBezier(
[float](54*$u), [float](82*$u), [float](41*$u), [float](78*$u),
[float](31*$u), [float](69.5*$u), [float](31*$u), [float](56*$u))
$s.AddLine([float](31*$u), [float](56*$u), [float](31*$u), [float](37*$u))
$s.CloseFigure()
$g.FillPath($brush, $s)
# Corps
$body = New-Object System.Drawing.Drawing2D.GraphicsPath
$bx = 34*$u; $by = 54*$u; $bw = 40*$u; $bh = 26*$u; $br = 4*$u
$body.AddArc([float]$bx, [float]$by, [float](2*$br), [float](2*$br), 180, 90)
$body.AddArc([float]($bx+$bw-2*$br), [float]$by, [float](2*$br), [float](2*$br), 270, 90)
$body.AddArc([float]($bx+$bw-2*$br), [float]($by+$bh-2*$br), [float](2*$br), [float](2*$br), 0, 90)
$body.AddArc([float]$bx, [float]($by+$bh-2*$br), [float](2*$br), [float](2*$br), 90, 90)
$body.CloseFigure()
$g.FillPath($brush, $body)
# Serrure, evidee dans la couleur du fond
# Serrure, evidee dans la couleur du fond. Le vecteur y arrive par un
# remplissage pair-impair ; ici, repeindre par-dessus revient au meme,
# l'icone ancienne etant de toute facon opaque.
$bgBrush = New-Object System.Drawing.SolidBrush $bg
$g.FillEllipse($bgBrush, [float](50*$u), [float](58*$u), [float](8*$u), [float](8*$u))
$g.FillRectangle($bgBrush, [float](52.4*$u), [float](64*$u), [float](3.2*$u), [float](9*$u))
$g.FillEllipse($bgBrush, [float](47*$u), [float](43*$u), [float](14*$u), [float](14*$u))
$slot = New-Object System.Drawing.Drawing2D.GraphicsPath
$slot.AddLines(@(
(New-Object System.Drawing.PointF ([float](51*$u), [float](55.5*$u))),
(New-Object System.Drawing.PointF ([float](57*$u), [float](55.5*$u))),
(New-Object System.Drawing.PointF ([float](58.6*$u), [float](70*$u))),
(New-Object System.Drawing.PointF ([float](49.4*$u), [float](70*$u)))))
$slot.CloseFigure()
$g.FillPath($bgBrush, $slot)
$g.Dispose()
$bmp.Save((Join-Path $out 'ic_launcher.png'), [System.Drawing.Imaging.ImageFormat]::Png)
$bmp.Dispose()