- Chats: Direktnachrichten + Gruppen ohne Server zusammen, Filter-Chips (Alle/Ungelesen/Personen/Gruppen), Leerzustaende mit Handlung; virtuelles 'Raeume'-Symbol entfaellt, Server-Kanaele nur noch im Server - Gruppen: runder Gruppen-Avatar, Kopf mit Mitgliederzahl, Anruf-/Video-Knopf - Gruppenanruf: LiveKit + Anwesenheit, Start-Nachricht (Push), Karte 'Gruppenanruf laeuft · Beitreten', Hinweis-Karte im Verlauf - Anrufansicht: aneinandergedrueckte Bubbles (Federphysik, Sprecher waechst), Kacheln umschaltbar, Bildschirm automatisch gross, Gespraechsdauer, Matrix-Namen/-Avatare statt LiveKit-IDs - Tests: test/bubble_physics_test.dart Co-Authored-By: Claude Opus 5.5 <[email protected]>
95 lines
3.5 KiB
Dart
95 lines
3.5 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
import 'package:pyramid/features/call_ui/bubble_physics.dart';
|
|
|
|
/// Lässt die Simulation [seconds] lang laufen (60 Schritte pro Sekunde).
|
|
bool _run(BubbleSim sim, double seconds) {
|
|
var moving = true;
|
|
for (var i = 0; i < (seconds * 60).round(); i++) {
|
|
moving = sim.step(1 / 60);
|
|
}
|
|
return moving;
|
|
}
|
|
|
|
void main() {
|
|
const phone = Size(390, 620);
|
|
const desktop = Size(1200, 700);
|
|
|
|
test('zwei Bubbles drücken sich leicht aneinander und kommen zur Ruhe', () {
|
|
final sim = BubbleSim()..sync(['a', 'b'], {}, desktop);
|
|
final moving = _run(sim, 6);
|
|
expect(moving, isFalse, reason: 'Simulation kommt nicht zur Ruhe');
|
|
|
|
final a = sim.bodies['a']!, b = sim.bodies['b']!;
|
|
final dist = (a.pos - b.pos).distance;
|
|
// Sie berühren sich (überlappen leicht) statt frei zu schweben …
|
|
expect(dist, lessThan(a.r + b.r));
|
|
// … liegen aber nicht ineinander.
|
|
expect(dist, greaterThan((a.r + b.r) * 0.8));
|
|
// Querformat: nebeneinander statt übereinander
|
|
expect((a.pos.dy - b.pos.dy).abs(), lessThan((a.pos.dx - b.pos.dx).abs()));
|
|
});
|
|
|
|
test('wer spricht, wird größer', () {
|
|
final sim = BubbleSim()..sync(['a', 'b', 'c'], {}, phone);
|
|
_run(sim, 4);
|
|
final before = sim.bodies['b']!.r;
|
|
sim.sync(['a', 'b', 'c'], {'b'}, phone);
|
|
_run(sim, 4);
|
|
final b = sim.bodies['b']!.r;
|
|
expect(b, greaterThan(before * 1.1));
|
|
expect(b, greaterThan(sim.bodies['a']!.r * 1.1));
|
|
});
|
|
|
|
test('viele Teilnehmer bleiben im Bild (Handy hochkant)', () {
|
|
final ids = [for (var i = 0; i < 8; i++) 'p$i'];
|
|
final sim = BubbleSim()..sync(ids, {}, phone);
|
|
_run(sim, 8);
|
|
for (final b in sim.bodies.values) {
|
|
expect(b.pos.dx - b.r, greaterThan(-2), reason: '${b.id} links raus');
|
|
expect(b.pos.dx + b.r, lessThan(phone.width + 2), reason: '${b.id} rechts raus');
|
|
expect(b.pos.dy - b.r, greaterThan(-2), reason: '${b.id} oben raus');
|
|
expect(b.pos.dy + b.r, lessThan(phone.height + 2), reason: '${b.id} unten raus');
|
|
}
|
|
});
|
|
|
|
test('Gehende schrumpfen weg und verschwinden', () {
|
|
final sim = BubbleSim()..sync(['a', 'b'], {}, phone);
|
|
_run(sim, 3);
|
|
sim.sync(['a'], {}, phone);
|
|
_run(sim, 3);
|
|
expect(sim.bodies.keys, ['a']);
|
|
});
|
|
|
|
test('Berührungsfläche ist abgeflacht, freier Rand bleibt rund', () {
|
|
final sim = BubbleSim()..sync(['a', 'b'], {}, desktop);
|
|
_run(sim, 6);
|
|
final a = sim.bodies['a']!, b = sim.bodies['b']!;
|
|
final bounds = sim.clipPath('a').getBounds();
|
|
final toB = (b.pos - a.pos);
|
|
// In Richtung des Nachbarn endet die Bubble vor ihrem Kreisrand …
|
|
if (toB.dx > 0) {
|
|
expect(bounds.right, lessThan(a.pos.dx + a.r - 1));
|
|
// … auf der freien Seite reicht sie bis zum Kreisrand.
|
|
expect(bounds.left, closeTo(a.pos.dx - a.r, 1));
|
|
} else {
|
|
expect(bounds.left, greaterThan(a.pos.dx - a.r + 1));
|
|
expect(bounds.right, closeTo(a.pos.dx + a.r, 1));
|
|
}
|
|
// Mittelpunkt liegt im Umriss, der Nachbar-Mittelpunkt nicht.
|
|
expect(sim.clipPath('a').contains(a.pos), isTrue);
|
|
expect(sim.clipPath('a').contains(b.pos), isFalse);
|
|
});
|
|
|
|
test('einzelne Bubble: rund, mittig', () {
|
|
final sim = BubbleSim()..sync(['solo'], {}, phone);
|
|
_run(sim, 4);
|
|
final s = sim.bodies['solo']!;
|
|
expect(s.pos.dx, closeTo(phone.width / 2, 2));
|
|
expect(s.pos.dy, closeTo(phone.height / 2, 2));
|
|
final bounds = sim.clipPath('solo').getBounds();
|
|
expect(bounds.width, closeTo(s.r * 2, 1));
|
|
});
|
|
}
|