Replace the default Flutter icon with a stylised cloud

The app shipped the Flutter template's blue F, which said nothing about what it
does and did not match the interface. The launcher icon is now a white cloud on
the same seed blue the app themes itself with, so the two read as one product.

The icon is drawn rather than hand-edited: tool/generate_icon.py renders it from
signed distance fields — a union of three circles and a rounded box — using only
the standard library, so regenerating it needs no image toolchain. The shape's
framing is derived from its own bounding box, which is what keeps it centred;
maintaining the bounds separately from the shape had it drifting off-centre.

Adds the adaptive icon and the Android 13 monochrome layer, which were missing
entirely. The generator frames the cloud against the 72dp the launcher mask
always keeps and adaptive_icon_foreground_inset is 0, so the layer is not shrunk
twice and the icon reads at the same size as its neighbours on a home screen.

Verified on the emulator: the cloud appears in the app drawer and holds its shape
down to 48px. docs/architecture.md records how to regenerate it, including the
flutter_launcher_icons 0.14.4 bug that writes a string into a boolean Xcode
setting and needs reverting by hand afterwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-10 22:44:33 +02:00
co-authored by Claude Opus 5
parent aa91b457ed
commit f434a68155
46 changed files with 473 additions and 81 deletions
+36 -1
View File
@@ -33,7 +33,7 @@ Nuvolari/
├─ app/ Flutter application (Dart package "nuvolari")
├─ backend/ Python worker
├─ docs/ this documentation
└─ tool/ verification scripts
└─ tool/ verification and asset-generation scripts
```
## App layers
@@ -131,3 +131,38 @@ subscribing to FCM topics named after geographic cells — the subscription happ
device, so the backend holds no user records at all. With no advertising and no forecast
provider, nothing about the user leaves the device except the area the map is looking at,
which is inherent to any hosted base map.
## Launcher icon
The icon is a white cloud on the app's seed blue `#1F6FB2`, so the launcher and the
interface are recognisably the same product. It is drawn, not hand-edited:
`tool/generate_icon.py` renders it from signed distance fields — a union of three
circles and one rounded box — and writes two 1024px sources into `app/assets/icon/`.
Those sources are not listed in the pubspec `assets:` block: they feed the build and are
never bundled into the app.
`flutter_launcher_icons` fans them out to the Android densities, the adaptive icon, the
Android 13 monochrome layer and the iOS set, all of which are committed.
To change it:
```
python tool/generate_icon.py
cd app && dart run flutter_launcher_icons
```
Two things to know before doing that.
**The generator owns the framing.** An adaptive layer is 108dp of which only the central
72dp survives the launcher's mask, so `ADAPTIVE_WIDTH` is measured against that safe zone
and `adaptive_icon_foreground_inset` is set to `0`. Left at its default of 16 the tool
would inset the layer a second time and the icon would read visibly smaller than every
other one on the home screen.
**flutter_launcher_icons 0.14.4 corrupts an unrelated Xcode setting.** `ios.dart:340`
rewrites the value of any line containing `ASSETCATALOG`, so it writes `AppIcon` into
`ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS`, which is a boolean.
Check `git diff app/ios/Runner.xcodeproj/project.pbxproj` after running it and restore
those lines to `YES`. It also minifies `AppIcon.appiconset/Contents.json` onto one line;
re-indenting it keeps the file reviewable.