Skip to content

Instantly share code, notes, and snippets.

@densa
Created April 17, 2024 07:35
Show Gist options
  • Save densa/286397e55b8b67ab9a6edc6229b5819e to your computer and use it in GitHub Desktop.
Save densa/286397e55b8b67ab9a6edc6229b5819e to your computer and use it in GitHub Desktop.
typing indicator
/// https://docs.flutter.dev/cookbook/effects/typing-indicator
import 'dart:math';
import 'package:core/design_system.dart';
class TypingIndicator extends StatefulWidget {
const TypingIndicator({super.key});
@override
State<TypingIndicator> createState() => _TypingIndicatorState();
}
class _TypingIndicatorState extends State<TypingIndicator>
with TickerProviderStateMixin {
final List<Interval> _dotIntervals = const [
Interval(0.25, 0.8),
Interval(0.35, 0.9),
Interval(0.45, 1.0),
];
late AnimationController _repeatingController;
@override
void initState() {
super.initState();
_repeatingController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1500),
);
_repeatingController.repeat();
}
@override
void dispose() {
_repeatingController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return StatusBubble(
repeatingController: _repeatingController, // <-- Add this
dotIntervals: _dotIntervals,
flashingCircleDarkColor: const Color(0xFFBFBFBF),
flashingCircleBrightColor: const Color(0xFFE0E0E0),
);
}
}
class StatusBubble extends StatelessWidget {
const StatusBubble({
super.key,
required this.repeatingController,
required this.dotIntervals,
required this.flashingCircleBrightColor,
required this.flashingCircleDarkColor,
});
final AnimationController repeatingController;
final List<Interval> dotIntervals;
final Color flashingCircleDarkColor;
final Color flashingCircleBrightColor;
@override
Widget build(BuildContext context) {
return Container(
width: 85,
height: 44,
padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
FlashingCircle(
index: 0,
repeatingController: repeatingController,
dotIntervals: dotIntervals,
flashingCircleDarkColor: flashingCircleDarkColor,
flashingCircleBrightColor: flashingCircleBrightColor,
),
FlashingCircle(
index: 1,
repeatingController: repeatingController,
dotIntervals: dotIntervals,
flashingCircleDarkColor: flashingCircleDarkColor,
flashingCircleBrightColor: flashingCircleBrightColor,
),
FlashingCircle(
index: 2,
repeatingController: repeatingController,
dotIntervals: dotIntervals,
flashingCircleDarkColor: flashingCircleDarkColor,
flashingCircleBrightColor: flashingCircleBrightColor,
),
],
),
);
}
}
class FlashingCircle extends StatelessWidget {
const FlashingCircle({
super.key,
required this.index,
required this.repeatingController,
required this.dotIntervals,
required this.flashingCircleBrightColor,
required this.flashingCircleDarkColor,
});
final int index;
final AnimationController repeatingController;
final List<Interval> dotIntervals;
final Color flashingCircleDarkColor;
final Color flashingCircleBrightColor;
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: repeatingController,
builder: (context, child) {
final circleFlashPercent = dotIntervals[index].transform(
repeatingController.value,
);
final circleColorPercent = sin(pi * circleFlashPercent);
return Container(
width: 12,
height: 12,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color.lerp(
flashingCircleDarkColor,
flashingCircleBrightColor,
circleColorPercent,
),
),
);
},
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment