Files
pyramid/lib/widgets/pyramid_loader.dart
Bernd Steckmeister 057ac1e4ea style: verbleibende flutter-analyze Deprecations beheben
withOpacity/activeColor/cacheExtent/PublicRoomsChunk durch die neuen
Flutter-3.44-Äquivalente ersetzt.
2026-07-03 09:40:10 +02:00

191 lines
5.0 KiB
Dart

import 'dart:math';
import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart';
import 'package:pyramid/core/theme.dart';
class PyramidLoader extends StatefulWidget {
final double size;
final bool useLottie;
const PyramidLoader({super.key, this.size = 60, this.useLottie = false});
@override
State<PyramidLoader> createState() => _PyramidLoaderState();
}
class _PyramidLoaderState extends State<PyramidLoader>
with SingleTickerProviderStateMixin {
late AnimationController _ctrl;
// Mirror the JS constants exactly
static const _hold = 0.2;
static const _curveExp = 3.0;
static const _tiltDeg = 20.0;
double _ease(double t, double exp) =>
t < 0.5 ? 0.5 * pow(2 * t, exp) : 1 - 0.5 * pow(2 * (1 - t), exp);
double _yawAt(double t) {
final a = 1.0 - _hold;
if (t >= a) return pi / 4;
return _ease(t / a, _curveExp) * 2 * pi + pi / 4;
}
@override
void initState() {
super.initState();
_ctrl = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
)..repeat();
}
@override
void dispose() {
_ctrl.dispose();
super.dispose();
}
Color _accent(BuildContext ctx) {
try {
return PyramidTheme.of(ctx).accent;
} catch (_) {
return const Color(0xFFF5A524);
}
}
@override
Widget build(BuildContext context) {
final accent = _accent(context);
final painter = AnimatedBuilder(
animation: _ctrl,
builder: (_, _) => CustomPaint(
painter: _PyramidPainter(
yaw: _yawAt(_ctrl.value),
accent: accent,
tiltDeg: _tiltDeg,
),
size: Size(widget.size, widget.size),
),
);
if (widget.useLottie) {
return Center(
child: SizedBox(
width: widget.size,
height: widget.size,
child: ColorFiltered(
colorFilter: ColorFilter.mode(accent, BlendMode.srcIn),
child: Lottie.asset(
'assets/pyramid-loader.json',
fit: BoxFit.contain,
repeat: true,
animate: true,
errorBuilder: (_, _, _) => painter,
),
),
),
);
}
return Center(child: painter);
}
}
class _PyramidPainter extends CustomPainter {
final double yaw;
final Color accent;
final double tiltDeg;
const _PyramidPainter({
required this.yaw,
required this.accent,
required this.tiltDeg,
});
List<double> _rotY(List<double> v, double a) {
final c = cos(a), s = sin(a);
return [v[0] * c + v[2] * s, v[1], -v[0] * s + v[2] * c];
}
List<List<double>> _clipZ(List<List<double>> poly) {
final out = <List<double>>[];
for (var i = 0; i < poly.length; i++) {
final a = poly[i];
final b = poly[(i + 1) % poly.length];
final ai = a[2] >= 0, bi = b[2] >= 0;
if (ai) out.add(a);
if (ai != bi) {
final t = a[2] / (a[2] - b[2]);
out.add([a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1]), 0.0]);
}
}
return out;
}
Offset _proj(List<double> v, double sc, double cx, double cy, double tr) =>
Offset(cx + v[0] * sc, cy + (v[1] * cos(tr) + v[2] * sin(tr)) * sc);
Color _darken(Color c, double amount) => c.withValues(
red: max(0.0, c.r * (1 - amount)),
green: max(0.0, c.g * (1 - amount)),
blue: max(0.0, c.b * (1 - amount)),
);
@override
void paint(Canvas canvas, Size size) {
final sc = size.width * 0.38;
final cx = size.width / 2;
final cy = size.height / 2;
final tr = -tiltDeg * pi / 180;
const base = 0.7;
final faces = [
[[0.0, -1.0, 0.0], [base, 1.0, base], [base, 1.0, -base]],
[[0.0, -1.0, 0.0], [base, 1.0, -base], [-base, 1.0, -base]],
[[0.0, -1.0, 0.0], [-base, 1.0, -base],[-base, 1.0, base]],
[[0.0, -1.0, 0.0], [-base, 1.0, base], [base, 1.0, base]],
];
final items = <({List<Offset> pts, double depth})>[];
for (final face in faces) {
final rot = face.map((v) => _rotY(v, yaw)).toList();
final cl = _clipZ(rot);
if (cl.length < 3) continue;
final depth = cl.fold(0.0, (s, v) => s + v[2]) / cl.length;
items.add((
pts: cl.map((v) => _proj(v, sc, cx, cy, tr)).toList(),
depth: depth,
));
}
items.sort((a, b) => a.depth.compareTo(b.depth));
final fillPaint = Paint()
..color = _darken(accent, 0.45)
..style = PaintingStyle.fill;
final strokePaint = Paint()
..color = accent
..style = PaintingStyle.stroke
..strokeWidth = 1.4 * (size.width / 96)
..strokeJoin = StrokeJoin.round
..strokeCap = StrokeCap.round;
for (final item in items) {
final path = Path()..moveTo(item.pts[0].dx, item.pts[0].dy);
for (var i = 1; i < item.pts.length; i++) {
path.lineTo(item.pts[i].dx, item.pts[i].dy);
}
path.close();
canvas.drawPath(path, fillPaint);
canvas.drawPath(path, strokePaint);
}
}
@override
bool shouldRepaint(_PyramidPainter old) =>
old.yaw != yaw || old.accent != accent;
}