// Copyright (C) 2023 WYATT GROUP // Please see the AUTHORS file for details. // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_animate/flutter_animate.dart'; import 'package:wyatt_component_copy_with_extension/component_copy_with_extension.dart'; import 'package:wyatt_ui_components/wyatt_wyatt_ui_components.dart'; import 'package:wyatt_ui_kit/src/components/loader/loader_theme_resolver.dart'; import 'package:wyatt_ui_kit/wyatt_ui_kit.dart'; part 'loader.g.dart'; @ComponentCopyWithExtension() class Loader extends LoaderComponent with $LoaderCWMixin { const Loader({ super.colors, super.radius, super.stroke, super.duration, super.flip, super.themeResolver, super.key, }); @override LoaderThemeResolver? get themeResolver => super.themeResolver as LoaderThemeResolver?; /// Negotiate the theme to get a complete style. LoaderStyle _resolve(BuildContext context) { final LoaderThemeResolver resolver = themeResolver ?? LoaderThemeResolver( computeExtensionValueFn: ( context, defaultValue, themeExtension, { extra, }) => LoaderStyle( colors: themeExtension.colors, stroke: themeExtension.stroke, ), customStyleFn: (context, {extra}) => LoaderStyle( colors: colors, stroke: stroke, ), ); return resolver.negotiate(context); } @override Widget build(BuildContext context) { final style = _resolve(context); final dimension = (radius != null) ? radius! * 2 : context.buttonTheme.height; return SizedBox.square( dimension: dimension, child: RepaintBoundary( child: CustomPaint( painter: _LoaderPainter( style.colors!, dimension / 2, style.stroke!, flip: flip ?? false, ), ) .animate( onPlay: (controller) => controller.repeat(), ) .rotate( duration: duration ?? 900.ms, begin: (flip ?? false) ? 0 : 1, end: (flip ?? false) ? 1 : 0, ), ), ); } } class _LoaderPainter extends CustomPainter { _LoaderPainter( this.colors, this.radius, this.stroke, { required this.flip, }); final MultiColor colors; final double radius; final double stroke; final bool flip; @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final circleSurface = Rect.fromCircle(center: center, radius: radius); final dotColor = colors.color; final dotCenter = Offset(size.width / 2 + (flip ? -radius : radius), size.height / 2); final gradient = colors.isGradient ? colors.colors : [colors.color, colors.color]; final gradientCirclePainter = Paint() ..shader = SweepGradient( colors: (flip ? gradient.reversed : gradient).toList(), transform: flip ? const GradientRotation(pi) : null, ).createShader(circleSurface) ..strokeWidth = stroke ..style = PaintingStyle.stroke ..strokeCap = StrokeCap.round; final dotPainter = Paint() ..color = dotColor ..style = PaintingStyle.fill ..strokeCap = StrokeCap.round; canvas ..drawCircle(center, radius, gradientCirclePainter) ..drawCircle(dotCenter, stroke / 2, dotPainter); } @override bool shouldRepaint(_LoaderPainter oldDelegate) => false; @override bool shouldRebuildSemantics(_LoaderPainter oldDelegate) => false; }