tasq/lib/widgets/typing_dots.dart

73 lines
1.8 KiB
Dart

import 'package:flutter/material.dart';
class TypingDots extends StatefulWidget {
const TypingDots({super.key, this.size = 6, this.color, this.spacing = 4});
final double size;
final double spacing;
final Color? color;
@override
State<TypingDots> createState() => _TypingDotsState();
}
class _TypingDotsState extends State<TypingDots>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
)..repeat();
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
double _opacity(double t, double offset) {
final phase = (t + offset) % 1.0;
final distance = (phase - 0.5).abs();
final ramp = 1.0 - (distance / 0.5);
return 0.3 + 0.7 * ramp.clamp(0.0, 1.0);
}
@override
Widget build(BuildContext context) {
final color =
widget.color ?? Theme.of(context).colorScheme.onSurfaceVariant;
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
final t = _controller.value;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
_dot(color, _opacity(t, 0.0)),
SizedBox(width: widget.spacing),
_dot(color, _opacity(t, 0.2)),
SizedBox(width: widget.spacing),
_dot(color, _opacity(t, 0.4)),
],
);
},
);
}
Widget _dot(Color color, double opacity) {
return Opacity(
opacity: opacity,
child: Container(
width: widget.size,
height: widget.size,
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
),
);
}
}