import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:immich_mobile/widgets/common/immich_logo.dart'; class ImmichLoadingIndicator extends HookWidget { final double? borderRadius; const ImmichLoadingIndicator({ super.key, this.borderRadius, }); @override Widget build(BuildContext context) { final logoAnimationController = useAnimationController( duration: const Duration(seconds: 6), ) ..reverse() ..repeat(); final borderAnimationController = useAnimationController( duration: const Duration(seconds: 6), )..repeat(); return Container( height: 80, width: 80, decoration: BoxDecoration( color: Colors.transparent, borderRadius: BorderRadius.circular(borderRadius ?? 50), backgroundBlendMode: BlendMode.luminosity, ), child: AnimatedBuilder( animation: borderAnimationController, builder: (context, child) { return CustomPaint( painter: GradientBorderPainter( animation: borderAnimationController.value, strokeWidth: 3, ), child: child, ); }, child: Padding( padding: const EdgeInsets.all(15), child: RotationTransition( turns: logoAnimationController, child: const ImmichLogo( heroTag: 'logo', ), ), ), ), ); } } class GradientBorderPainter extends CustomPainter { final double animation; final double strokeWidth; final double opacity = 0.7; final colors = [ const Color(0xFFFA2921), const Color(0xFFED79B5), const Color(0xFFFFB400), const Color(0xFF1E83F7), const Color(0xFF18C249), ]; GradientBorderPainter({ required this.animation, required this.strokeWidth, }); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = min(size.width, size.height) / 2 - strokeWidth / 2; // Create a sweep gradient that covers the entire circle final Rect rect = Rect.fromCircle(center: center, radius: radius); // Create a paint with the gradient final paint = Paint() ..style = PaintingStyle.stroke ..strokeWidth = strokeWidth; // Create a gradient that smoothly transitions between colors final shader = SweepGradient( // Use a fixed starting point and let matrix transformation handle rotation startAngle: 0, endAngle: 2 * 3.14159, colors: [ // Repeat colors to ensure smooth transitions ...colors.map((c) => c.withValues(alpha: opacity)), colors.first.withValues(alpha: opacity), ], // Add evenly distributed stops stops: List.generate( colors.length + 1, (index) => index / colors.length, ), tileMode: TileMode.clamp, // Use transformations to rotate the gradient transform: GradientRotation(-animation * 2 * 3.14159), ).createShader(rect); paint.shader = shader; // Draw the circular border canvas.drawCircle(center, radius, paint); } @override bool shouldRepaint(GradientBorderPainter oldDelegate) { return animation != oldDelegate.animation; } double min(double a, double b) => a < b ? a : b; }