From 65596eee2b91382767928be287d7606e6a32e0a9 Mon Sep 17 00:00:00 2001 From: Darek Date: Mon, 13 Apr 2026 04:28:11 -0500 Subject: [PATCH] =?UTF-8?q?Add=20PWA=20support=20=E2=80=94=20installable?= =?UTF-8?q?=20as=20home=20screen=20app?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Web App Manifest, a minimal Service Worker, and Apple/Android meta tags so the app can be added to a phone home screen and opens full-screen in standalone mode (no browser chrome). - static/manifest.json: name, short_name, display=standalone, icons - static/sw.js: minimal SW served at /sw.js (root scope) via new Flask route - static/icon-192.png, icon-512.png: generated by sbin/gen_icons.py (stdlib only) - base.html: manifest link, theme-color, apple-mobile-web-app-* tags, SW registration --- app.py | 7 ++++++- sbin/gen_icons.py | 31 +++++++++++++++++++++++++++++++ static/icon-192.png | Bin 0 -> 413 bytes static/icon-512.png | Bin 0 -> 1496 bytes static/manifest.json | 12 ++++++++++++ static/sw.js | 5 +++++ templates/base.html | 11 +++++++++++ 7 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 sbin/gen_icons.py create mode 100644 static/icon-192.png create mode 100644 static/icon-512.png create mode 100644 static/manifest.json create mode 100644 static/sw.js 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 0000000000000000000000000000000000000000..6cb1d700c76e779f90534e11f90d4f55f2cc3246 GIT binary patch literal 413 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE2}s`E_d9@rficC?#WAGf*4s;tybKH+M>ZHY z8$C&8;%+IQtQ9fsRpWM#hDHMcwnGjH3Op@9mIMcLq5=;KPyonkgh)wnfTh@&KvFG@ z2S8GZ3SeCbMYPZlw}edl@#^PU!i3^WY#zXG05p^iUS(RhZI9uPoUKL-K;Y@>=d#Wz Gp$P!3kz~aH literal 0 HcmV?d00001 diff --git a/static/icon-512.png b/static/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..9dda08535218309ef4c1e3696508141b182028d5 GIT binary patch literal 1496 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&t&wwUqN(1_svoo-U3d6}R4AHRNSr;9%Kk z-fZ+FnTdam$;zf^y{pUzoF_6IU|=v{VBle3U?z)6vGx#(tOvT964!&Zi<0kqNT87& zcvR62^gnq~2iHcSb{?`44%~JsXjh>;5m1x{3>XGH4LPtP1t?ie3R3}v+NqEV20Rgv dn1&i=vnIR`` {% block title %}Battery Tracker{% endblock %} + + + + + +