@ -1,7 +1,8 @@
import ' package:flutter/material.dart ' ;
import ' package:immich_mobile/extensions/build_context_extensions.dart ' ;
import ' package:flutter_hooks/flutter_hooks.dart ' ;
import ' package:immich_mobile/widgets/common/immich_logo.dart ' ;
class ImmichLoadingIndicator extends Stateless Widget {
class ImmichLoadingIndicator extends Hook Widget {
final double ? borderRadius ;
const ImmichLoadingIndicator ( {
@ -11,18 +12,109 @@ class ImmichLoadingIndicator extends StatelessWidget {
@ 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: 60 ,
width: 60 ,
height: 8 0,
width: 8 0,
decoration: BoxDecoration (
color: context . primaryColor . withAlpha ( 200 ) ,
borderRadius: BorderRadius . circular ( borderRadius ? ? 10 ) ,
color: Colors . transparent ,
borderRadius: BorderRadius . circular ( borderRadius ? ? 50 ) ,
backgroundBlendMode: BlendMode . luminosity ,
) ,
padding: const EdgeInsets . all ( 15 ) ,
child: const CircularProgressIndicator (
color: Colors . white ,
strokeWidth: 3 ,
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 ;
}