Files
pyramid/test/inflated_layout_test.dart
Bernd SteckmeisterandClaude Opus 5.5 ab84701e73 feat(call): aufgeblasene Formen statt runder Bubbles (Bernds Referenzbild)
- Neues Layout lib/features/call_ui/inflated_layout.dart: gewichtetes
  Voronoi-/Potenzdiagramm im Anrufbereich, Zellen fuellen das Fenster bis an
  die Raender, an Nachbarn plattgedrueckt, weich gerundete Ecken;
  Lloyd-Relaxation + sanfter Zug ins Raster (aufgeraeumt), Flaechen per
  Gewicht nachgefuehrt (Sprecher 1,7-fach), Neue pumpen sich auf, Gehende
  schrumpfen weg, je mehr Personen desto kleiner
- Darstellung nur als Kontur mit weichem Innenleuchten, Profilbild mit
  Kontur + Name in der Mitte, Sprecher gruen pulsierend, Kamera-Bild
  fuellt die Form
- Tests: test/inflated_layout_test.dart (7), Vorschau-/Animationsbilder
  integration_test/call_bubbles_demo_test.dart; alte Kugel-Physik entfernt
- Version 0.1.43

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-10 14:54:45 +02:00

108 lines
3.9 KiB
Dart

import 'dart:ui';
import 'package:flutter_test/flutter_test.dart';
import 'package:pyramid/features/call_ui/inflated_layout.dart';
/// [seconds] lang simulieren (60 Schritte pro Sekunde).
bool _run(InflatedLayout l, double seconds) {
var moving = true;
for (var i = 0; i < (seconds * 60).round(); i++) {
moving = l.step(1 / 60);
}
return moving;
}
List<String> _ids(int n) => [for (var i = 0; i < n; i++) 'p$i'];
void main() {
const phone = Size(390, 620);
const desktop = Size(1200, 700);
test('eine Person füllt den ganzen Bereich bis an die Ränder', () {
final l = InflatedLayout()..sync(['solo'], {}, phone);
_run(l, 1);
final r = l.cellBounds('solo');
expect(r.left, closeTo(l.margin, 0.5));
expect(r.top, closeTo(l.margin, 0.5));
expect(r.right, closeTo(phone.width - l.margin, 0.5));
expect(r.bottom, closeTo(phone.height - l.margin, 0.5));
// Gerundete Ecken: der Umriss enthält die Ecke des Rechtecks nicht mehr.
final path = l.outline('solo');
expect(path.contains(r.topLeft + const Offset(2, 2)), isFalse);
expect(path.contains(r.center), isTrue);
});
test('vier Personen: gleich große Zellen, zusammen fast die ganze Fläche', () {
final l = InflatedLayout()..sync(_ids(4), {}, desktop);
final moving = _run(l, 6);
expect(moving, isFalse, reason: 'kommt nicht zur Ruhe');
final areas = l.cells.values.map((c) => c.area).toList();
final avg = areas.reduce((a, b) => a + b) / areas.length;
for (final a in areas) {
expect(a, closeTo(avg, avg * 0.08));
}
final total = areas.reduce((a, b) => a + b);
expect(total, greaterThan(l.bounds.width * l.bounds.height * 0.9));
});
test('wer spricht, bekommt deutlich mehr Fläche', () {
final l = InflatedLayout()..sync(_ids(3), {}, phone);
_run(l, 4);
l.sync(_ids(3), {'p1'}, phone);
_run(l, 5);
final speaker = l.cells['p1']!.area;
final other = l.cells['p0']!.area;
expect(speaker / other,
closeTo(InflatedLayout.speakingShare, InflatedLayout.speakingShare * 0.15));
});
test('je mehr Personen, desto kleiner die Zellen', () {
double avgArea(int n) {
final l = InflatedLayout()..sync(_ids(n), {}, phone);
_run(l, 5);
return l.cells.values.fold<double>(0, (s, c) => s + c.area) / n;
}
final a2 = avgArea(2), a5 = avgArea(5), a9 = avgArea(9);
expect(a5, lessThan(a2));
expect(a9, lessThan(a5));
});
test('Neue pumpen sich auf, Gehende verschwinden', () {
final l = InflatedLayout()..sync(_ids(2), {}, phone);
_run(l, 3);
l.sync(_ids(3), {}, phone);
expect(l.cells['p2']!.area, lessThan(l.cells['p0']!.area));
_run(l, 5);
expect(l.cells['p2']!.area, closeTo(l.cells['p0']!.area, l.cells['p0']!.area * 0.1));
l.sync(['p0', 'p2'], {}, phone);
_run(l, 3);
expect(l.cells.keys.toSet(), {'p0', 'p2'});
});
test('Zellen überlappen nicht und jeder Mittelpunkt liegt in seiner Zelle', () {
final l = InflatedLayout()..sync(_ids(7), {'p3'}, desktop);
_run(l, 6);
final paths = {for (final id in l.cells.keys) id: l.outline(id)};
for (final c in l.cells.values) {
expect(paths[c.id]!.contains(c.centroid), isTrue, reason: '${c.id} Schwerpunkt');
for (final other in l.cells.values) {
if (other.id == c.id) continue;
expect(paths[other.id]!.contains(c.centroid), isFalse,
reason: '${c.id} liegt in ${other.id}');
}
}
});
test('Drehen (hochkant → quer): zwei Personen nebeneinander', () {
final l = InflatedLayout()..sync(_ids(2), {}, phone);
_run(l, 3);
final a = l.cells['p0']!.centroid, b = l.cells['p1']!.centroid;
expect((a.dy - b.dy).abs(), greaterThan((a.dx - b.dx).abs()), reason: 'hochkant: übereinander');
l.sync(_ids(2), {}, desktop);
_run(l, 3);
final c = l.cells['p0']!.centroid, d = l.cells['p1']!.centroid;
expect((c.dx - d.dx).abs(), greaterThan((c.dy - d.dy).abs()), reason: 'quer: nebeneinander');
});
}