Refonte du logo et des icônes aux couleurs du thème (Android, Fire TV, iOS)

- Nouveau logo vectoriel : bouton play dégradé cyan→bleu (#0A84FF) avec
  traînées de vitesse, sur tuile graphite (#2C2C33→#1B1B1F) comme l'app
- scripts/generate_icons.py génère toutes les tailles depuis les SVG
  (assets/branding/)
- Android : icône adaptative dans la zone sûre, icône ronde, écran de
  lancement graphite avec logo
- Android TV / Fire TV : bannière 320x180, LEANBACK_LAUNCHER, écran
  tactile et leanback non requis
- iOS : icône carrée sans alpha dédiée (app_icon_ios.png)
- Remplace l'ancien logo (JPEG avec damier incrusté) et corrige la
  référence à logo_xtremobile.png inexistant dans les réglages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GyudLt8TgggqbWWkqjB19K
This commit is contained in:
Claude committed 2026-09-30 07:34:38 +00:00
1 parent 462bcc784c
commit 03126d3b36
36 files changed
+425 -26

No files matched your search

+8
View File
@@ -3,10 +3,17 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- Android TV / Fire TV : l'app reste installable sur téléphone
et apparaît dans le lanceur TV (pas d'écran tactile requis). -->
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
<uses-feature android:name="android.software.leanback" android:required="false"/>
<application
android:label="XtremFlow"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:banner="@drawable/banner"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
@@ -28,6 +35,7 @@
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
<category android:name="android.intent.category.LEANBACK_LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<!-- Écran de lancement : fond graphite du thème + logo centré -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<item android:drawable="@color/ic_launcher_background" />
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@drawable/ic_launcher_foreground" />
</item>
</layer-list>
Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 183 KiB

After

Width:  |  Height:  |  Size: 22 KiB

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<!-- Écran de lancement : fond graphite du thème + logo centré -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<item android:drawable="@color/ic_launcher_background" />
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
android:src="@drawable/ic_launcher_foreground" />
</item>
</layer-list>
@@ -1,9 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Icône adaptative : le calque avant est déjà dessiné dans la zone sûre
(66dp sur 108dp), aucun inset supplémentaire n'est nécessaire.
Généré par scripts/generate_icons.py -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground>
<inset
android:drawable="@drawable/ic_launcher_foreground"
android:inset="16%" />
</foreground>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Icône adaptative : le calque avant est déjà dessiné dans la zone sûre
(66dp sur 108dp), aucun inset supplémentaire n'est nécessaire.
Généré par scripts/generate_icons.py -->
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

+2 -1
View File
@@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1a1a2e</color>
<!-- baseLevel0 de lib/core/theme/app_colors.dart -->
<color name="ic_launcher_background">#1B1B1F</color>
</resources>
+37
View File
@@ -0,0 +1,37 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2C2C33"/>
<stop offset="1" stop-color="#1B1B1F"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#0A84FF" stop-opacity="0.35"/>
<stop offset="1" stop-color="#0A84FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="#7DD3F2"/>
<stop offset="0.55" stop-color="#0A84FF"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="#7DD3F2" stop-opacity="0"/>
<stop offset="1" stop-color="#7DD3F2" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<g transform="translate(215.04 215.04) scale(5.9392)">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

+40
View File
@@ -0,0 +1,40 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2C2C33"/>
<stop offset="1" stop-color="#1B1B1F"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#0A84FF" stop-opacity="0.35"/>
<stop offset="1" stop-color="#0A84FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="#7DD3F2"/>
<stop offset="0.55" stop-color="#0A84FF"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="#7DD3F2" stop-opacity="0"/>
<stop offset="1" stop-color="#7DD3F2" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<circle cx="512.0" cy="512.0" r="512.0" fill="url(#bg)"/>
<circle cx="512.0" cy="512.0" r="512.0" fill="url(#shine)"/>
<g transform="translate(194.56 194.56) scale(6.3488)">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

+42
View File
@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2C2C33"/>
<stop offset="1" stop-color="#1B1B1F"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#0A84FF" stop-opacity="0.35"/>
<stop offset="1" stop-color="#0A84FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="#7DD3F2"/>
<stop offset="0.55" stop-color="#0A84FF"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="#7DD3F2" stop-opacity="0"/>
<stop offset="1" stop-color="#7DD3F2" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="1024" height="1024" rx="230.4" fill="url(#bg)"/>
<rect width="1024" height="1024" rx="230.4" fill="url(#shine)"/>
<rect x="1.5" y="1.5" width="1021" height="1021" rx="228.9"
fill="none" stroke="#FFFFFF" stroke-opacity="0.06" stroke-width="3"/>
<g transform="translate(184.32 184.32) scale(6.5536)">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+42
View File
@@ -0,0 +1,42 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2C2C33"/>
<stop offset="1" stop-color="#1B1B1F"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#0A84FF" stop-opacity="0.35"/>
<stop offset="1" stop-color="#0A84FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="#7DD3F2"/>
<stop offset="0.55" stop-color="#0A84FF"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="#7DD3F2" stop-opacity="0"/>
<stop offset="1" stop-color="#7DD3F2" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="1024" height="1024" rx="0" fill="url(#bg)"/>
<rect width="1024" height="1024" rx="0" fill="url(#shine)"/>
<rect x="1.5" y="1.5" width="1021" height="1021" rx="0"
fill="none" stroke="#FFFFFF" stroke-opacity="0.06" stroke-width="3"/>
<g transform="translate(184.32 184.32) scale(6.5536)">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

+44
View File
@@ -0,0 +1,44 @@
<svg xmlns="http://www.w3.org/2000/svg" width="320" height="180" viewBox="0 0 320 180">
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2C2C33"/>
<stop offset="1" stop-color="#1B1B1F"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="#0A84FF" stop-opacity="0.35"/>
<stop offset="1" stop-color="#0A84FF" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="#7DD3F2"/>
<stop offset="0.55" stop-color="#0A84FF"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="#7DD3F2" stop-opacity="0"/>
<stop offset="1" stop-color="#7DD3F2" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>
<rect width="320" height="180" fill="url(#bg)"/>
<rect width="320" height="180" fill="url(#shine)"/>
<g transform="translate(29.60 39.60) scale(1.0080)">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>
<text x="140.8" y="101.0" font-family="Inter" font-weight="bold"
font-size="32" fill="#F5F5F7" letter-spacing="-0.6">Xtrem<tspan fill="#8AB9FF">Flow</tspan></text>
<text x="141.8" y="124.0" font-family="Inter" font-weight="600"
font-size="12" fill="#98989F" letter-spacing="2.4">IPTV PLAYER</text>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 KiB

After

Width:  |  Height:  |  Size: 38 KiB

+1 -1
View File
@@ -5,6 +5,6 @@
flutter_launcher_icons:
ios: true
android: false
image_path: "assets/images/logo_xtremflow.png"
image_path: "assets/images/app_icon_ios.png"
# L'App Store et iOS refusent une icône avec canal alpha.
remove_alpha_ios: true
@@ -85,7 +85,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
height: 48,
// No decoration for free-floating logo
child: Image.asset(
'assets/images/logo_xtremobile.png',
'assets/images/logo_xtremflow.png',
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => Icon(
Icons.play_circle_filled,
+4 -4
View File
@@ -79,12 +79,12 @@ dev_dependencies:
riverpod_generator: ^2.4.0
hive_generator: ^2.0.1
# Les icônes Android (mipmap, adaptative, bannière TV) sont générées par
# scripts/generate_icons.py et versionnées : ne pas les écraser ici.
flutter_launcher_icons:
android: true
android: false
ios: true
image_path: "assets/images/logo_xtremflow.png"
adaptive_icon_background: "#1a1a2e"
adaptive_icon_foreground: "assets/images/logo_xtremflow.png"
image_path: "assets/images/app_icon_ios.png"
# iOS refuse une icône comportant un canal alpha.
remove_alpha_ios: true
+182
View File
@@ -0,0 +1,182 @@
#!/usr/bin/env python3
"""Génère toutes les icônes XtremFlow à partir d'un seul dessin vectoriel.
Palette alignée sur lib/core/theme/app_colors.dart (graphite + bleu Apple).
Sorties :
assets/branding/*.svg sources vectorielles
assets/images/logo_xtremflow.png logo in-app (fond transparent)
assets/images/app_icon_ios.png icône iOS (carré plein, sans alpha)
android/.../mipmap-*/ic_launcher.png icône legacy (tuile arrondie)
android/.../mipmap-*/ic_launcher_round.png icône legacy ronde
android/.../drawable-*/ic_launcher_foreground.png calque adaptatif
android/.../drawable-xhdpi/banner.png bannière Android TV / Fire TV
Usage : pip install cairosvg pillow && python3 scripts/generate_icons.py
La bannière utilise la police Inter (Bold) si elle est installée.
"""
import io
import os
import cairosvg
from PIL import Image
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
RES = os.path.join(ROOT, "android", "app", "src", "main", "res")
# Couleurs du thème (app_colors.dart)
BG_TOP = "#2C2C33" # surfaceContainerLow
BG_BOTTOM = "#1B1B1F" # baseLevel0
BLUE = "#0A84FF" # primaryContainer
BLUE_LIGHT = "#8AB9FF" # primary
CYAN = "#7DD3F2" # tertiary
DEFS = f"""
<defs>
<linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="{BG_TOP}"/>
<stop offset="1" stop-color="{BG_BOTTOM}"/>
</linearGradient>
<radialGradient id="glow" cx="0.5" cy="0.5" r="0.5">
<stop offset="0" stop-color="{BLUE}" stop-opacity="0.35"/>
<stop offset="1" stop-color="{BLUE}" stop-opacity="0"/>
</radialGradient>
<linearGradient id="play" x1="0.2" y1="0" x2="0.9" y2="1">
<stop offset="0" stop-color="{CYAN}"/>
<stop offset="0.55" stop-color="{BLUE}"/>
<stop offset="1" stop-color="#0A5BD6"/>
</linearGradient>
<linearGradient id="streak" gradientUnits="userSpaceOnUse" x1="0" y1="0" x2="28" y2="0">
<stop offset="0" stop-color="{CYAN}" stop-opacity="0"/>
<stop offset="1" stop-color="{CYAN}" stop-opacity="1"/>
</linearGradient>
<linearGradient id="shine" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#FFFFFF" stop-opacity="0.07"/>
<stop offset="0.5" stop-color="#FFFFFF" stop-opacity="0"/>
</linearGradient>
</defs>"""
def mark(cx, cy, size):
"""Le symbole : un bouton play fluide précédé de traînées de vitesse.
Dessiné dans une boîte de 100x100 centrée sur (cx, cy), mise à l'échelle
à `size` pixels.
"""
s = size / 100.0
tx, ty = cx - 50 * s, cy - 50 * s
return f"""
<g transform="translate({tx:.2f} {ty:.2f}) scale({s:.4f})">
<circle cx="56" cy="50" r="50" fill="url(#glow)"/>
<path d="M40 22 L84 50 L40 78 Z" fill="url(#play)" stroke="url(#play)"
stroke-width="12" stroke-linejoin="round"/>
<path d="M44 34 L66 48" stroke="#FFFFFF" stroke-opacity="0.28"
stroke-width="4" stroke-linecap="round"/>
<g stroke="url(#streak)" stroke-linecap="round" fill="none">
<path d="M6 38 L28 38" stroke-width="7"/>
<path d="M0 50 L28 50" stroke-width="7"/>
<path d="M10 62 L28 62" stroke-width="7"/>
</g>
</g>"""
def svg(w, h, body):
return (
f'<svg xmlns="http://www.w3.org/2000/svg" width="{w}" height="{h}" '
f'viewBox="0 0 {w} {h}">{DEFS}{body}\n</svg>\n'
)
def tile(size, radius_ratio, mark_ratio=0.64):
"""Tuile graphite (arrondie ou pleine) avec le symbole centré."""
r = size * radius_ratio
body = f"""
<rect width="{size}" height="{size}" rx="{r}" fill="url(#bg)"/>
<rect width="{size}" height="{size}" rx="{r}" fill="url(#shine)"/>
<rect x="1.5" y="1.5" width="{size - 3}" height="{size - 3}" rx="{max(r - 1.5, 0)}"
fill="none" stroke="#FFFFFF" stroke-opacity="0.06" stroke-width="3"/>
{mark(size / 2, size / 2, size * mark_ratio)}"""
return svg(size, size, body)
def round_tile(size):
body = f"""
<circle cx="{size / 2}" cy="{size / 2}" r="{size / 2}" fill="url(#bg)"/>
<circle cx="{size / 2}" cy="{size / 2}" r="{size / 2}" fill="url(#shine)"/>
{mark(size / 2, size / 2, size * 0.62)}"""
return svg(size, size, body)
def adaptive_foreground(size):
# 108dp de canevas, zone sûre de 66dp : le symbole occupe ~58% du canevas,
# soit l'intérieur du cercle sûr quelle que soit la forme du masque.
return svg(size, size, mark(size / 2, size / 2, size * 0.58))
def banner(w=320, h=180):
"""Bannière TV (Android TV / Fire TV) : 320x180 px en xhdpi."""
body = f"""
<rect width="{w}" height="{h}" fill="url(#bg)"/>
<rect width="{w}" height="{h}" fill="url(#shine)"/>
{mark(w * 0.25, h / 2, h * 0.56)}
<text x="{w * 0.44}" y="{h / 2 + 11}" font-family="Inter" font-weight="bold"
font-size="32" fill="#F5F5F7" letter-spacing="-0.6">Xtrem<tspan fill="{BLUE_LIGHT}">Flow</tspan></text>
<text x="{w * 0.44 + 1}" y="{h / 2 + 34}" font-family="Inter" font-weight="600"
font-size="12" fill="#98989F" letter-spacing="2.4">IPTV PLAYER</text>"""
return svg(w, h, body)
def render(svg_text, out_path, size=None, flatten=None):
png = cairosvg.svg2png(
bytestring=svg_text.encode(),
output_width=size[0] if size else None,
output_height=size[1] if size else None,
)
img = Image.open(io.BytesIO(png)).convert("RGBA")
if flatten:
bg = Image.new("RGBA", img.size, flatten)
bg.alpha_composite(img)
img = bg.convert("RGB")
os.makedirs(os.path.dirname(out_path), exist_ok=True)
img.save(out_path, optimize=True)
print(" ", os.path.relpath(out_path, ROOT), img.size)
def main():
brand = os.path.join(ROOT, "assets", "branding")
os.makedirs(brand, exist_ok=True)
sources = {
"icon_rounded.svg": tile(1024, 0.225),
"icon_square.svg": tile(1024, 0),
"icon_round.svg": round_tile(1024),
"icon_foreground.svg": adaptive_foreground(1024),
"tv_banner.svg": banner(),
}
for name, text in sources.items():
with open(os.path.join(brand, name), "w") as f:
f.write(text)
print("Assets Flutter :")
render(sources["icon_rounded.svg"], os.path.join(ROOT, "assets/images/logo_xtremflow.png"), (512, 512))
# iOS applique son propre masque et refuse l'alpha : carré plein.
render(sources["icon_square.svg"], os.path.join(ROOT, "assets/images/app_icon_ios.png"), (1024, 1024), flatten=BG_BOTTOM)
print("Android :")
densities = {"mdpi": 1, "hdpi": 1.5, "xhdpi": 2, "xxhdpi": 3, "xxxhdpi": 4}
for d, k in densities.items():
legacy = round(48 * k)
fg = round(108 * k)
render(sources["icon_rounded.svg"], f"{RES}/mipmap-{d}/ic_launcher.png", (legacy, legacy))
render(sources["icon_round.svg"], f"{RES}/mipmap-{d}/ic_launcher_round.png", (legacy, legacy))
render(sources["icon_foreground.svg"], f"{RES}/drawable-{d}/ic_launcher_foreground.png", (fg, fg))
# Bannière TV : 160x90dp -> 320x180 en xhdpi (taille de référence Google/Amazon).
render(sources["tv_banner.svg"], f"{RES}/drawable-xhdpi/banner.png", (320, 180), flatten=BG_BOTTOM)
render(sources["tv_banner.svg"], f"{RES}/drawable-xxhdpi/banner.png", (480, 270), flatten=BG_BOTTOM)
render(sources["tv_banner.svg"], f"{RES}/drawable-xxxhdpi/banner.png", (640, 360), flatten=BG_BOTTOM)
# Aperçu grand format pour les stores / README.
render(sources["tv_banner.svg"], os.path.join(brand, "tv_banner_1280x720.png"), (1280, 720), flatten=BG_BOTTOM)
if __name__ == "__main__":
main()