import 'dart:math' as math; import 'package:flutter/material.dart'; import 'package:conduit/l10n/app_localizations.dart'; /// Animated iiEasy logo: 3 rotating dashed rings, exactly as in the reference HTML. /// - Outer: rotate 0° → 360°, 10s /// - Middle: rotate 30° → -330°, 12s /// - Inner: rotate 60° → 420°, 8s class IiEasyLoadingLogo extends StatefulWidget { final bool isDark; const IiEasyLoadingLogo({super.key, required this.isDark}); @override State createState() => _IiEasyLoadingLogoState(); } class _IiEasyLoadingLogoState extends State with TickerProviderStateMixin { late AnimationController _outerController; late AnimationController _middleController; late AnimationController _innerController; @override void initState() { super.initState(); _outerController = AnimationController( vsync: this, duration: const Duration(seconds: 10), )..repeat(); _middleController = AnimationController( vsync: this, duration: const Duration(seconds: 12), )..repeat(); _innerController = AnimationController( vsync: this, duration: const Duration(seconds: 8), )..repeat(); } @override void dispose() { _outerController.dispose(); _middleController.dispose(); _innerController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final strokeColor = widget.isDark ? Colors.white : const Color(0xFF1F2937); final textColor = widget.isDark ? Colors.white : const Color(0xFF1F2937); final sloganColor = widget.isDark ? Colors.grey.shade300 : Colors.grey.shade700; return ColoredBox( color: widget.isDark ? const Color(0xFF000000) : Colors.white, child: SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ SizedBox( width: 200, height: 200, child: AnimatedBuilder( animation: Listenable.merge([ _outerController, _middleController, _innerController, ]), builder: (context, _) { return CustomPaint( painter: _RingsPainter( strokeColor: strokeColor, outerTurns: _outerController.value, middleTurns: _middleController.value, innerTurns: _innerController.value, ), size: const Size(200, 200), ); }, ), ), const SizedBox(height: 32), Text( 'iiEasy', style: TextStyle( fontSize: 48, fontWeight: FontWeight.bold, color: textColor, letterSpacing: -1, ), ), const SizedBox(height: 8), Text( AppLocalizations.of(context)?.appSlogan ?? 'Future. Simple.', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, color: sloganColor, letterSpacing: 0.5, ), ), ], ), ), ), ); } } /// Draws 3 dashed circles with rotation exactly as in HTML. /// viewBox 0 0 200 200, center (100, 100). class _RingsPainter extends CustomPainter { _RingsPainter({ required this.strokeColor, required this.outerTurns, required this.middleTurns, required this.innerTurns, }); final Color strokeColor; final double outerTurns; // 0..1 → 0°..360° final double middleTurns; // 0..1 → 30°..-330° (so use 30/360 + (1-t)*2 - 1?) final double innerTurns; // 0..1 → 60°..420° static const double cx = 100; static const double cy = 100; @override void paint(Canvas canvas, Size size) { final scale = size.width / 200; canvas.save(); canvas.scale(scale); final paint = Paint() ..color = strokeColor ..style = PaintingStyle.stroke ..strokeWidth = 6 ..strokeCap = StrokeCap.butt; // Outer: 0° → 360° in 10s. angle = outerTurns * 2*pi _drawDashedCircle(canvas, paint, cx, cy, 70, [15, 85], outerTurns * 2 * math.pi); // Middle: 30° → -330° in 12s. So angle = 30° + (1 - middleTurns) * 360° going backwards // 30° = 30/360 * 2pi, -330° = -330/360 * 2pi = 30/360 * 2pi - 2pi final middleAngle = (30 / 360) * 2 * math.pi - middleTurns * 2 * math.pi; _drawDashedCircle(canvas, paint, cx, cy, 50, [12, 58], middleAngle); // Inner: 60° → 420° in 8s. angle = 60° + innerTurns * 360° final innerAngle = (60 / 360) * 2 * math.pi + innerTurns * 2 * math.pi; _drawDashedCircle(canvas, paint, cx, cy, 30, [8, 32], innerAngle); canvas.restore(); } void _drawDashedCircle(Canvas canvas, Paint paint, double cx, double cy, double r, List dashArray, double startAngleRad) { const twoPi = 2 * math.pi; // SVG stroke-dasharray: dash and gap in user units along the path. // Arc length = r * angle, so angle = length / r. final dashAngle = dashArray[0] / r; final gapAngle = dashArray[1] / r; final periodAngle = dashAngle + gapAngle; double angle = startAngleRad; final rect = Rect.fromCircle(center: Offset(cx, cy), radius: r); while (angle < startAngleRad + twoPi) { final sweep = math.min(dashAngle, startAngleRad + twoPi - angle); if (sweep > 0.001) { final path = Path()..arcTo(rect, angle, sweep, false); canvas.drawPath(path, paint); } angle += periodAngle; } } @override bool shouldRepaint(_RingsPainter oldDelegate) { return oldDelegate.outerTurns != outerTurns || oldDelegate.middleTurns != middleTurns || oldDelegate.innerTurns != innerTurns || oldDelegate.strokeColor != strokeColor; } }