135 lines
4.0 KiB
Dart
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;
|
|
}
|