135 lines
4.0 KiB
Dart

// 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 <https://www.gnu.org/licenses/>.
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(
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 ?? const MultiColor([]),
dimension / 2,
style.stroke ?? 4,
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;
}