import 'package:flutter/material.dart'; import '../theme/app_theme.dart'; /// Animated 3-dot typing indicator styled as an assistant bubble. class TypingIndicator extends StatefulWidget { const TypingIndicator({super.key}); @override State createState() => _TypingIndicatorState(); } class _TypingIndicatorState extends State 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(); } @override Widget build(BuildContext context) { final isDark = Theme.of(context).brightness == Brightness.dark; return Align( alignment: Alignment.centerLeft, child: Container( margin: const EdgeInsets.only(left: 16, bottom: 8, right: 80), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: isDark ? AppColors.assistantBubble : AppColors.lightAssistantBubble, borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), bottomRight: Radius.circular(16), bottomLeft: Radius.circular(4), ), ), child: AnimatedBuilder( animation: _controller, builder: (context, _) { return Row( mainAxisSize: MainAxisSize.min, children: List.generate(3, (i) { final delay = i * 0.2; final t = (_controller.value - delay).clamp(0.0, 1.0); // Bounce: sin wave for smooth up/down final offset = -4.0 * _bounce(t); return Transform.translate( offset: Offset(0, offset), child: Container( width: 8, height: 8, margin: EdgeInsets.only(right: i < 2 ? 4 : 0), decoration: BoxDecoration( color: isDark ? AppColors.darkTextSecondary : AppColors.lightTextSecondary, shape: BoxShape.circle, ), ), ); }), ); }, ), ), ); } double _bounce(double t) { // Simple sine bounce if (t <= 0 || t >= 1) return 0; return (t < 0.5) ? (t * 2) * (t * 2) // ease in : 1 - ((t - 0.5) * 2) * ((t - 0.5) * 2); // ease out } }