diff --git a/app.py b/app.py index 26a0250..259d6a8 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, redirect, url_for, request, flash, abort +from flask import Flask, render_template, redirect, url_for, request, flash, abort, send_from_directory from sqlalchemy import create_engine, func from sqlalchemy.orm import scoped_session, sessionmaker @@ -25,6 +25,11 @@ def create_app(config_object="config"): # Dashboard # ------------------------------------------------------------------ # + @app.route("/sw.js") + def service_worker(): + return send_from_directory(app.static_folder, "sw.js", + mimetype="application/javascript") + @app.route("/") def dashboard(): batteries = db.query(Battery).order_by(Battery.label).all() diff --git a/sbin/gen_icons.py b/sbin/gen_icons.py new file mode 100644 index 0000000..7ca41e3 --- /dev/null +++ b/sbin/gen_icons.py @@ -0,0 +1,31 @@ +"""Generate solid-color PNG icons for PWA manifest using stdlib only (no Pillow).""" +import zlib, struct, os + +def make_png(size, rgb=(0x25, 0x63, 0xEB)): + """Create a minimal valid RGB PNG of the given size filled with one color.""" + w = h = size + raw = b'' + for _ in range(h): + raw += b'\x00' + bytes(rgb) * w # filter byte 0 (None) + RGB pixels + + compressed = zlib.compress(raw, 9) + + def chunk(name, data): + c = name + data + return struct.pack('>I', len(data)) + c + struct.pack('>I', zlib.crc32(c) & 0xffffffff) + + sig = b'\x89PNG\r\n\x1a\n' + ihdr = chunk(b'IHDR', struct.pack('>IIBBBBB', w, h, 8, 2, 0, 0, 0)) + idat = chunk(b'IDAT', compressed) + iend = chunk(b'IEND', b'') + return sig + ihdr + idat + iend + + +if __name__ == '__main__': + out_dir = os.path.join(os.path.dirname(__file__), '..', 'static') + os.makedirs(out_dir, exist_ok=True) + for size in [192, 512]: + path = os.path.join(out_dir, f'icon-{size}.png') + with open(path, 'wb') as f: + f.write(make_png(size)) + print(f'Wrote {path}') diff --git a/static/icon-192.png b/static/icon-192.png new file mode 100644 index 0000000..6cb1d70 Binary files /dev/null and b/static/icon-192.png differ diff --git a/static/icon-512.png b/static/icon-512.png new file mode 100644 index 0000000..9dda085 Binary files /dev/null and b/static/icon-512.png differ diff --git a/static/manifest.json b/static/manifest.json new file mode 100644 index 0000000..0e694e2 --- /dev/null +++ b/static/manifest.json @@ -0,0 +1,12 @@ +{ + "name": "Battery Tracker", + "short_name": "Batteries", + "start_url": "/", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#2563eb", + "icons": [ + { "src": "/static/icon-192.png", "sizes": "192x192", "type": "image/png" }, + { "src": "/static/icon-512.png", "sizes": "512x512", "type": "image/png" } + ] +} diff --git a/static/sw.js b/static/sw.js new file mode 100644 index 0000000..05206ed --- /dev/null +++ b/static/sw.js @@ -0,0 +1,5 @@ +// Minimal service worker — required for PWA installability +self.addEventListener('install', function(e) { self.skipWaiting(); }); +self.addEventListener('activate', function(e) { e.waitUntil(clients.claim()); }); +// No caching — app requires live server data +self.addEventListener('fetch', function(e) {}); diff --git a/templates/base.html b/templates/base.html index 751fc4e..a373208 100644 --- a/templates/base.html +++ b/templates/base.html @@ -4,6 +4,12 @@