- 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]>
112 lines
3.9 KiB
Dart
112 lines
3.9 KiB
Dart
import 'dart:io';
|
||
import 'dart:ui' as ui;
|
||
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter/rendering.dart';
|
||
import 'package:flutter_test/flutter_test.dart';
|
||
import 'package:integration_test/integration_test.dart';
|
||
import 'package:pyramid/core/theme.dart';
|
||
import 'package:pyramid/features/call_ui/voice_channel.dart' show CallBubblesDemo;
|
||
|
||
/// Vorschaubilder der Anrufansicht mit erfundenen Teilnehmern (ohne Login):
|
||
/// Standbilder für 1–7 Personen (Handy + PC) und eine Bildfolge der
|
||
/// Animation (Sprecherwechsel, jemand kommt dazu, jemand geht) für ein GIF.
|
||
/// Bilder: build/call_demo/.
|
||
///
|
||
/// flutter test integration_test/call_bubbles_demo_test.dart -d windows
|
||
void main() {
|
||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||
|
||
testWidgets('Anruf-Formen: Vorschaubilder', (tester) async {
|
||
final count = ValueNotifier(5);
|
||
final speaking = ValueNotifier(2);
|
||
void phone() => tester.view
|
||
..physicalSize = const Size(1080, 2340)
|
||
..devicePixelRatio = 2.625;
|
||
void desktop() => tester.view
|
||
..physicalSize = const Size(1600, 1000)
|
||
..devicePixelRatio = 1.0;
|
||
addTearDown(tester.view.reset);
|
||
|
||
Future<void> smooth(Duration d) async {
|
||
final end = DateTime.now().add(d);
|
||
while (DateTime.now().isBefore(end)) {
|
||
await tester.pump(const Duration(milliseconds: 16));
|
||
}
|
||
}
|
||
|
||
phone();
|
||
await tester.pumpWidget(PyramidTheme(
|
||
child: MaterialApp(
|
||
debugShowCheckedModeBanner: false,
|
||
theme: buildPyramidThemeData(ThemeVariant.soft),
|
||
home: ValueListenableBuilder<int>(
|
||
valueListenable: count,
|
||
builder: (_, n, _) => ValueListenableBuilder<int>(
|
||
valueListenable: speaking,
|
||
builder: (_, s, _) => CallBubblesDemo(count: n, speaking: s),
|
||
),
|
||
),
|
||
),
|
||
));
|
||
|
||
// ── Standbilder ──
|
||
for (final n in [1, 2, 3, 5, 7]) {
|
||
count.value = n;
|
||
speaking.value = n >= 3 ? 2 : -1;
|
||
phone();
|
||
await smooth(const Duration(milliseconds: 2500));
|
||
await _shot(tester, 'p_${n}_personen');
|
||
desktop();
|
||
await smooth(const Duration(milliseconds: 2500));
|
||
await _shot(tester, 'd_${n}_personen');
|
||
}
|
||
|
||
// ── Animation (Handy) ──
|
||
phone();
|
||
count.value = 4;
|
||
speaking.value = -1;
|
||
await smooth(const Duration(seconds: 3));
|
||
var frame = 0;
|
||
Future<void> record(Duration d) async {
|
||
final end = DateTime.now().add(d);
|
||
while (DateTime.now().isBefore(end)) {
|
||
await tester.pump(const Duration(milliseconds: 16));
|
||
await _shot(tester, 'anim/f${(frame++).toString().padLeft(4, '0')}',
|
||
scale: 0.4, log: false);
|
||
}
|
||
}
|
||
|
||
await record(const Duration(milliseconds: 600));
|
||
speaking.value = 1; // Anna spricht
|
||
await record(const Duration(milliseconds: 1600));
|
||
speaking.value = 3; // Clara spricht
|
||
await record(const Duration(milliseconds: 1600));
|
||
speaking.value = -1;
|
||
count.value = 5; // David kommt dazu
|
||
await record(const Duration(milliseconds: 1800));
|
||
count.value = 4; // David geht wieder
|
||
await record(const Duration(milliseconds: 1800));
|
||
// ignore: avoid_print
|
||
print('Animation: $frame Bilder');
|
||
}, timeout: const Timeout(Duration(minutes: 6)));
|
||
}
|
||
|
||
Future<void> _shot(WidgetTester tester, String name,
|
||
{double scale = 1, bool log = true}) async {
|
||
final renderView = tester.binding.renderViews.first;
|
||
final layer = renderView.debugLayer! as OffsetLayer;
|
||
final physical = tester.view.physicalSize;
|
||
final image = await tester.binding.runAsync(
|
||
() => layer.toImage(Offset.zero & physical, pixelRatio: scale),
|
||
);
|
||
final bytes = await tester.binding.runAsync(
|
||
() => image!.toByteData(format: ui.ImageByteFormat.png),
|
||
);
|
||
final file = File('build/call_demo/$name.png');
|
||
file.parent.createSync(recursive: true);
|
||
file.writeAsBytesSync(bytes!.buffer.asUint8List());
|
||
// ignore: avoid_print
|
||
if (log) print('Bild $name');
|
||
}
|