mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-18 20:29:44 -05:00
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:
@@ -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: |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 (
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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%
|
||||
@@ -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())
|
||||
@@ -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 $?
|
||||
Reference in New Issue
Block a user