feat(train): OpenFreeMap's dark style in a dark theme, and MAP_DARK_STYLE_URL for a provider's

The maps follow the theme where the source names a dark style, which
only the instance's own map did: OpenFreeMap, the default source, had
none, so a dark phone showed Liberty's bright map. The default now
hands OpenFreeMap's Dark style beside Liberty, and an operator setting
MAP_STYLE_URL may set MAP_DARK_STYLE_URL (HTTPS, else none) beside it,
passed on by compose.train.yaml and named in .env.example.
ADR 0030, D65.

Whats-New: The map is dark in a dark theme.
Whats-New-De: Die Karte ist im dunklen Design dunkel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-04 20:44:47 +01:00
commit 1c67310c93
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64
2 changed files with 39 additions and 4 deletions

View file

@ -3,10 +3,12 @@ import 'dart:io';
import 'package:halkyon_train_server/src/generated/protocol.dart';
/// The map source where the operator sets none (ADR 0030, D11): OpenFreeMap,
/// which takes no key and makes no contract, its Liberty style.
/// which takes no key and makes no contract, its Liberty style, and its Dark
/// one in a dark theme (D65).
final openFreeMap = MapSource(
name: 'OpenFreeMap',
styleUrl: 'https://tiles.openfreemap.org/styles/liberty',
darkStyleUrl: 'https://tiles.openfreemap.org/styles/dark',
attribution: 'OpenFreeMap © OpenMapTiles Data from OpenStreetMap',
aboutUrl: 'https://openfreemap.org',
);
@ -21,7 +23,8 @@ MapConfig mapConfig() => mapConfigFrom(Platform.environment);
/// The map [environment] sets. `MAP_TILES_URL` (D50), the instance's own
/// Protomaps tiles as an HTTPS `{z}/{x}/{y}` template, drawn by Train's
/// style, served beside them at `/map/style/light.json` and `dark.json`;
/// else `MAP_STYLE_URL`, a provider's MapLibre style over HTTPS; else
/// else `MAP_STYLE_URL`, a provider's MapLibre style over HTTPS, with
/// `MAP_DARK_STYLE_URL` its dark one (HTTPS, else none, D65); else
/// [openFreeMap]. Both with `MAP_SOURCE_NAME` (else the host),
/// `MAP_ATTRIBUTION` (else OpenStreetMap's credit, and Protomaps' for the
/// instance's tiles) and `MAP_ABOUT_URL` (HTTPS, else none).
@ -59,6 +62,7 @@ MapConfig mapConfigFrom(Map<String, String> environment) {
source: MapSource(
name: set('MAP_SOURCE_NAME') ?? style.host,
styleUrl: style.toString(),
darkStyleUrl: _https(environment['MAP_DARK_STYLE_URL'])?.toString(),
attribution: set('MAP_ATTRIBUTION') ?? '© OpenStreetMap contributors',
aboutUrl: about,
),

View file

@ -11,6 +11,8 @@ void main() {
expect(source.styleUrl, 'https://tiles.openfreemap.org/styles/liberty');
expect(source.attribution, contains('OpenStreetMap'));
expect(source.aboutUrl, 'https://openfreemap.org');
// Its dark style in a dark theme (D65).
expect(source.darkStyleUrl, 'https://tiles.openfreemap.org/styles/dark');
});
test("an operator's provider is named as they set it", () {
@ -31,6 +33,26 @@ void main() {
expect(source.aboutUrl, 'https://www.maptiler.com/privacy-policy/');
});
test("an operator's provider has a dark style where they set one over "
'HTTPS (D65), none otherwise', () {
const style = 'https://maps.example.org/light.json';
expect(
mapSourceFrom(const {
'MAP_STYLE_URL': style,
'MAP_DARK_STYLE_URL': 'https://maps.example.org/dark.json',
}).darkStyleUrl,
'https://maps.example.org/dark.json',
);
for (final dark in [null, '', 'http://maps.example.org/dark.json']) {
expect(
mapSourceFrom({'MAP_STYLE_URL': style, 'MAP_DARK_STYLE_URL': ?dark})
.darkStyleUrl,
isNull,
reason: dark,
);
}
});
test('a provider without a name is named by its host, and credits '
'OpenStreetMap', () {
final source = mapSourceFrom(const {