Files
Melo/test/player/cover_farbe_test.dart
Hermes (Server) 60c57577a3 Cover-Farbe, Teilen aus YouTube, Android Auto, geschärfte Typografie
P3-Liste aus dem UX-Review: Player nimmt Cover-Farbe auf, "Teilen" aus
YouTube trägt die Adresse in den Downloader ein, Android Auto blättert
durch die Bibliothek, Laufweite der Typografie geschärft, Überschriften
vereinheitlicht. 533 Tests grün, flutter analyze ohne Befund.
2026-08-25 09:09:27 +02:00

99 lines
3.2 KiB
Dart

import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:melo/player/cover_farbe.dart';
import 'package:melo/shared/theme.dart';
/// Baut ein RGBA-Bild aus Farben — je Farbe [wiederholungen] Bildpunkte.
Uint8List _bild(List<(Color, int)> teile) {
final bytes = <int>[];
for (final (farbe, anzahl) in teile) {
for (var i = 0; i < anzahl; i++) {
bytes.addAll([
(farbe.r * 255).round(),
(farbe.g * 255).round(),
(farbe.b * 255).round(),
(farbe.a * 255).round(),
]);
}
}
return Uint8List.fromList(bytes);
}
/// Der Player war für jeden Titel gleich schwarz. Diese Regel holt die
/// prägende Farbe aus dem Coverbild.
void main() {
group('dominanteFarbe', () {
test('ein einfarbiges Bild ergibt seine Farbe', () {
const rot = Color(0xFFD32F2F);
final farbe = dominanteFarbe(_bild([(rot, 100)]));
// Grob gerundet — die Farben werden in Töpfe einsortiert.
expect((farbe.r * 255).round(), closeTo(211, 16));
expect((farbe.g * 255).round(), closeTo(47, 16));
expect((farbe.b * 255).round(), closeTo(47, 16));
});
test('eine kräftige Farbe schlägt viel Grau', () {
// Cover sind oft überwiegend dunkel oder grau. Die häufigste Farbe wäre
// dann ein Grauton, und der Player sähe für jeden Titel gleich aus.
final farbe = dominanteFarbe(_bild([
(const Color(0xFF808080), 900),
(const Color(0xFF00A0FF), 100),
]));
expect(farbe.b, greaterThan(farbe.r));
});
test('ein rein schwarzes Bild fällt auf die Markenfarbe zurück', () {
expect(dominanteFarbe(_bild([(const Color(0xFF000000), 100)])),
MeloTheme.red);
});
test('ein rein weißes Bild fällt ebenfalls zurück', () {
expect(dominanteFarbe(_bild([(const Color(0xFFFFFFFF), 100)])),
MeloTheme.red);
});
test('durchsichtige Bildpunkte zählen nicht mit', () {
final farbe = dominanteFarbe(_bild([
(const Color(0x0000FF00), 900),
(const Color(0xFFD32F2F), 100),
]));
expect(farbe.r, greaterThan(farbe.g));
});
test('ein leeres Bild fällt auf die Markenfarbe zurück', () {
expect(dominanteFarbe(Uint8List(0)), MeloTheme.red);
});
test('unvollständige Bildpunkte am Ende stören nicht', () {
// Abgeschnittene Daten dürfen keinen Bereichsfehler auslösen.
final bytes = _bild([(const Color(0xFFD32F2F), 10)]).toList()
..addAll([12, 34]);
expect(() => dominanteFarbe(Uint8List.fromList(bytes)), returnsNormally);
});
});
group('lesbarAuf', () {
test('dunkle Farben bleiben dunkel genug für weiße Schrift', () {
final farbe = lesbarAuf(const Color(0xFFFFEB3B));
// Ein knalliges Gelb muss abgedunkelt werden, sonst ist weiße Schrift
// darauf nicht mehr lesbar.
expect(farbe.computeLuminance(), lessThan(0.35));
});
test('eine bereits dunkle Farbe bleibt weitgehend erhalten', () {
const dunkel = Color(0xFF1A237E);
expect(lesbarAuf(dunkel).computeLuminance(),
closeTo(dunkel.computeLuminance(), 0.1));
});
});
}