Build: add icon generation to CI and scripts, bump version to 4.16.0

- Run app icon and launcher icon generation in build-mobile workflow
- Add generate-mobile-icon scripts (Python/Pillow, ImageMagick, Inkscape)
- BUILD.md: document icon requirements and troubleshooting
- setup.py: version 4.16.0

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Dries Peeters
2026-02-01 16:51:13 +01:00
parent 9066089a34
commit 94fc19f6f2
10 changed files with 195 additions and 1 deletions
+18
View File
@@ -37,6 +37,14 @@ jobs:
working-directory: mobile
run: flutter pub get
- name: Generate app icons
run: |
pip install Pillow
python scripts/generate-mobile-icon.py
- name: Generate launcher icons
working-directory: mobile
run: dart run flutter_launcher_icons
# Skip tests for now - mobile app is incomplete (tests exist but lib/ source code is missing)
# - name: Run tests
# working-directory: mobile
@@ -74,6 +82,16 @@ jobs:
working-directory: mobile
run: flutter create --platforms=ios .
- name: Generate app icons
run: |
pip install Pillow
python scripts/generate-mobile-icon.py
- name: Generate launcher icons
working-directory: mobile
run: |
dart run flutter_launcher_icons
dart run flutter_launcher_icons -f flutter_launcher_icons_ios.yaml
- name: Configure iOS project for device build without code signing
working-directory: mobile
run: |
+4
View File
@@ -87,6 +87,7 @@ scripts\build-all.bat --windows-only
- Flutter SDK 3.0+
- Android SDK (for Android)
- Xcode (for iOS, macOS only)
- **App icon:** Launcher icons are generated at the start of each mobile build from `mobile/assets/icon/app_icon.png`. That PNG can be exported once from `app/static/images/timetracker-logo-icon.svg` (1024×1024), or created by running `scripts/generate-mobile-icon.bat` / `scripts/generate-mobile-icon.sh` (requires ImageMagick, Inkscape, or Python with Pillow).
### Desktop App
- Node.js 18+
@@ -107,6 +108,9 @@ scripts\build-all.bat --windows-only
## Troubleshooting
- **Mobile launcher icon shows Android default:** Run icon generation and do a full clean build: from `mobile/` run `flutter clean`, `flutter pub get`, `dart run flutter_launcher_icons`, then build again. The build scripts run icon generation automatically; if you built without them, run the above once.
- **Icon should match the web app:** Export `app/static/images/timetracker-logo-icon.svg` to 1024×1024 PNG at `mobile/assets/icon/app_icon.png` (see `mobile/assets/icon/README.md`), then run `dart run flutter_launcher_icons` and rebuild.
See `scripts/README-BUILD.md` for detailed troubleshooting guide.
## CI/CD
+12
View File
@@ -123,6 +123,18 @@ if "%BUILD_MOBILE%"=="1" (
echo [OK] Dependencies installed
echo.
echo [1b/6] Generating app icons...
cd /d "%PROJECT_ROOT%"
call "%SCRIPT_DIR%generate-mobile-icon.bat"
cd /d "%PROJECT_ROOT%\mobile"
call dart run flutter_launcher_icons
if errorlevel 1 (
echo [ERROR] Failed to generate launcher icons
exit /b 1
)
echo [OK] Launcher icons generated
echo.
echo [2/6] Analyzing Flutter code...
call flutter analyze
if errorlevel 1 (
+10
View File
@@ -84,6 +84,16 @@ build_mobile() {
# Get dependencies
print_header "Installing Flutter dependencies"
flutter pub get
# Generate app icons (source PNG then launcher sizes)
cd "$PROJECT_ROOT"
"$SCRIPT_DIR/generate-mobile-icon.sh" || true
cd "$PROJECT_ROOT/mobile"
dart run flutter_launcher_icons
if [ $? -ne 0 ]; then
print_error "Failed to generate launcher icons"
exit 1
fi
# Analyze code
print_header "Analyzing Flutter code"
+12
View File
@@ -41,6 +41,18 @@ if errorlevel 1 (
)
echo.
REM Generate app icons (source PNG then launcher sizes)
cd /d "%PROJECT_ROOT%"
call "%SCRIPT_DIR%generate-mobile-icon.bat"
cd /d "%MOBILE_DIR%"
echo Generating launcher icons...
call dart run flutter_launcher_icons
if errorlevel 1 (
echo ERROR: Failed to generate launcher icons
exit /b 1
)
echo.
REM Analyze
echo Analyzing code...
call flutter analyze
+12
View File
@@ -38,6 +38,18 @@ echo "Installing dependencies..."
flutter pub get
echo ""
# Generate app icons (source PNG then launcher sizes)
cd "$PROJECT_ROOT"
"$SCRIPT_DIR/generate-mobile-icon.sh" || true
cd "$MOBILE_DIR"
echo "Generating launcher icons..."
dart run flutter_launcher_icons
if [ $? -ne 0 ]; then
echo "ERROR: Failed to generate launcher icons"
exit 1
fi
echo ""
# Analyze
echo "Analyzing code..."
flutter analyze || true
+23
View File
@@ -0,0 +1,23 @@
@echo off
REM Generate mobile app icon (app_icon.png) from SVG or Python fallback.
setlocal
set SCRIPT_DIR=%~dp0
set PROJECT_ROOT=%SCRIPT_DIR%..
set SVG=%PROJECT_ROOT%\app\static\images\timetracker-logo-icon.svg
set OUT=%PROJECT_ROOT%\mobile\assets\icon\app_icon.png
if not exist "%PROJECT_ROOT%\mobile\assets\icon" mkdir "%PROJECT_ROOT%\mobile\assets\icon"
REM Try ImageMagick first (exact SVG export)
where magick >nul 2>&1
if %errorlevel% equ 0 (
magick "%SVG%" -resize 1024x1024 "%OUT%"
if %errorlevel% equ 0 (
echo Generated app_icon.png with ImageMagick
exit /b 0
)
)
REM Fallback: Python script (requires Pillow)
python "%SCRIPT_DIR%generate-mobile-icon.py"
exit /b %errorlevel%
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env python3
"""
Generate mobile app icon (app_icon.png) matching the web app's timetracker-logo-icon.svg.
Creates a 1024x1024 PNG: gradient rounded rect, white clock circle, hour marks, checkmark.
Requires: pip install Pillow
"""
import os
import sys
def main():
script_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(script_dir)
out_dir = os.path.join(project_root, "mobile", "assets", "icon")
out_path = os.path.join(out_dir, "app_icon.png")
try:
from PIL import Image, ImageDraw
except ImportError:
print("Pillow not installed. Run: pip install Pillow")
print("Or use ImageMagick: magick app/static/images/timetracker-logo-icon.svg -resize 1024x1024 mobile/assets/icon/app_icon.png")
return 1
os.makedirs(out_dir, exist_ok=True)
# Match SVG: 512 viewBox scaled to 1024 (scale 2)
size = 1024
r_rect = 256 # 128 * 2
cx, cy = size // 2, size // 2
r_clock = 360 # 180 * 2
stroke_circle = 64 # 32 * 2
stroke_mark = 48 # 24 * 2
stroke_check = 80 # 40 * 2
# 1) Gradient image (linear top-left #4A90E2 to bottom-right #50E3C2)
grad = Image.new("RGB", (size, size), (0, 0, 0))
px = grad.load()
for y in range(size):
for x in range(size):
t = (x + y) / (2 * size)
t = max(0, min(1, t))
r = int(0x4A + (0x50 - 0x4A) * t)
g = int(0x90 + (0xE3 - 0x90) * t)
b = int(0xE2 + (0xC2 - 0xE2) * t)
px[x, y] = (r, g, b)
# 2) Rounded rect mask
mask = Image.new("L", (size, size), 0)
ImageDraw.Draw(mask).rounded_rectangle([0, 0, size - 1, size - 1], radius=r_rect, fill=255)
# 3) Base image: gradient only inside rounded rect
base = Image.new("RGB", (size, size), (0x4A, 0x90, 0xE2))
base.paste(grad, (0, 0), mask)
draw = ImageDraw.Draw(base)
# 4) White circle (stroke only): outer circle white, inner circle = mid gradient
draw.ellipse([cx - r_clock, cy - r_clock, cx + r_clock, cy + r_clock], fill="white", outline=None)
inner_r = r_clock - stroke_circle
mid = ((0x4A + 0x50) // 2, (0x90 + 0xE3) // 2, (0xE2 + 0xC2) // 2)
draw.ellipse([cx - inner_r, cy - inner_r, cx + inner_r, cy + inner_r], fill=mid, outline=None)
# 5) Hour marks (4 white lines) - SVG positions scaled *2
draw.line([(cx, cy - r_clock), (cx, cy - r_clock + stroke_mark)], fill="white", width=stroke_mark)
draw.line([(cx, cy + r_clock - stroke_mark), (cx, cy + r_clock)], fill="white", width=stroke_mark)
draw.line([(cx - r_clock, cy), (cx - r_clock + stroke_mark, cy)], fill="white", width=stroke_mark)
draw.line([(cx + r_clock - stroke_mark, cy), (cx + r_clock, cy)], fill="white", width=stroke_mark)
# 6) Checkmark: M 195 270 L 255 330 L 365 220, stroke 40, round. Scale *2
draw.line([(390, 540), (510, 660), (730, 440)], fill="white", width=stroke_check, joint="curve")
base.save(out_path)
print(f"Created {out_path}")
return 0
if __name__ == "__main__":
sys.exit(main())
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
# Generate mobile app icon (app_icon.png) from SVG or Python fallback.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SVG="$PROJECT_ROOT/app/static/images/timetracker-logo-icon.svg"
OUT="$PROJECT_ROOT/mobile/assets/icon/app_icon.png"
mkdir -p "$(dirname "$OUT")"
# Try ImageMagick first (exact SVG export)
if command -v magick &> /dev/null; then
if magick "$SVG" -resize 1024x1024 "$OUT"; then
echo "Generated app_icon.png with ImageMagick"
exit 0
fi
fi
# Try Inkscape
if command -v inkscape &> /dev/null; then
if inkscape "$SVG" -w 1024 -h 1024 -o "$OUT"; then
echo "Generated app_icon.png with Inkscape"
exit 0
fi
fi
# Fallback: Python script (requires Pillow)
python3 "$SCRIPT_DIR/generate-mobile-icon.py"
exit $?
+1 -1
View File
@@ -7,7 +7,7 @@ from setuptools import setup, find_packages
setup(
name='timetracker',
version='4.15.1',
version='4.16.0',
packages=find_packages(),
include_package_data=True,
install_requires=[