Add PWA support — installable as home screen app
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
This commit is contained in:
@@ -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()
|
||||
|
||||
@@ -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}')
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 413 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -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" }
|
||||
]
|
||||
}
|
||||
@@ -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) {});
|
||||
@@ -4,6 +4,12 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{% block title %}Battery Tracker{% endblock %}</title>
|
||||
<link rel="manifest" href="/static/manifest.json">
|
||||
<meta name="theme-color" content="#2563eb">
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default">
|
||||
<meta name="apple-mobile-web-app-title" content="Batteries">
|
||||
<link rel="apple-touch-icon" href="/static/icon-192.png">
|
||||
<style>
|
||||
/* ─── Color variables (light mode) ──────────────────────────────── */
|
||||
:root {
|
||||
@@ -320,5 +326,10 @@
|
||||
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
<script>
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user