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]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d4473c7d64
commit
ab84701e73
@@ -0,0 +1,107 @@
|
||||
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');
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user