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,111 @@
|
||||
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');
|
||||
}
|
||||
Reference in New Issue
Block a user