mirror of
https://github.com/DRYTRIX/TimeTracker.git
synced 2026-05-24 07:10:21 -05:00
8fc823c252
Add app/static/manifest.json (TimeTracker / Tracker, indigo theme) and PNG install icons via scripts/generate_pwa_icons.py. Replace inline Flask service worker with app/static/js/sw.js served at /service-worker.js for full-site scope. Cache name timetracker-v1: cache-first for /static, network-first for HTML and non-v1 /api, no interception of /api/v1/* (preserves Authorization). Add public GET /offline and offline.html for SW navigation fallback; redirect /manifest.webmanifest to the static manifest. Wire base.html (manifest link, theme-color #4F46E5, SW registration) and pwa-enhancements.js (ready/update/push without duplicate registration). Remove legacy app/static/service-worker.js and manifest.webmanifest. Tests: service worker and offline routes, manifest redirect, TestPWA expectations; drop duplicate test_enhanced_ui app/client fixtures in favor of conftest. Docs: ASSETS.md, BUILD_CONFIGURATION.md, implementation notes, and incomplete-features analysis updated for new paths.
74 lines
2.8 KiB
Python
74 lines
2.8 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate android-chrome-192x192.png and android-chrome-512x512.png for PWA (matches timetracker icon style)."""
|
|
import os
|
|
import sys
|
|
|
|
try:
|
|
from PIL import Image, ImageDraw
|
|
except ImportError:
|
|
print("Pillow required: pip install Pillow", file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
|
|
def build_icon(size: int) -> Image.Image:
|
|
"""Raster icon matching scripts/generate-mobile-icon.py style, scaled to size."""
|
|
scale = size / 1024.0
|
|
r_rect = int(round(256 * scale))
|
|
cx, cy = size // 2, size // 2
|
|
r_clock = int(round(360 * scale))
|
|
stroke_circle = int(round(64 * scale))
|
|
stroke_mark = int(round(48 * scale))
|
|
stroke_check = int(round(80 * scale))
|
|
|
|
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)
|
|
|
|
mask = Image.new("L", (size, size), 0)
|
|
ImageDraw.Draw(mask).rounded_rectangle([0, 0, size - 1, size - 1], radius=r_rect, fill=255)
|
|
|
|
base = Image.new("RGB", (size, size), (0x4A, 0x90, 0xE2))
|
|
base.paste(grad, (0, 0), mask)
|
|
draw = ImageDraw.Draw(base)
|
|
|
|
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)
|
|
|
|
draw.line([(cx, cy - r_clock), (cx, cy - r_clock + stroke_mark)], fill="white", width=max(1, stroke_mark))
|
|
draw.line([(cx, cy + r_clock - stroke_mark), (cx, cy + r_clock)], fill="white", width=max(1, stroke_mark))
|
|
draw.line([(cx - r_clock, cy), (cx - r_clock + stroke_mark, cy)], fill="white", width=max(1, stroke_mark))
|
|
draw.line([(cx + r_clock - stroke_mark, cy), (cx + r_clock, cy)], fill="white", width=max(1, stroke_mark))
|
|
|
|
draw.line(
|
|
[(int(390 * scale), int(540 * scale)), (int(510 * scale), int(660 * scale)), (int(730 * scale), int(440 * scale))],
|
|
fill="white",
|
|
width=max(1, stroke_check),
|
|
joint="curve",
|
|
)
|
|
return base
|
|
|
|
|
|
def main() -> int:
|
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
project_root = os.path.dirname(script_dir)
|
|
out_dir = os.path.join(project_root, "app", "static", "images")
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
for name, dim in (("android-chrome-192x192.png", 192), ("android-chrome-512x512.png", 512)):
|
|
path = os.path.join(out_dir, name)
|
|
build_icon(dim).save(path, "PNG")
|
|
print(f"Wrote {path}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|