From 41f2739075cf6e4e0fb547098b7985fd26855f88 Mon Sep 17 00:00:00 2001 From: cogwheel0 <172976095+cogwheel0@users.noreply.github.com> Date: Mon, 8 Sep 2025 00:05:13 +0530 Subject: [PATCH] refactor: animation dots --- .../widgets/assistant_message_widget.dart | 134 ++++++++++++++---- lib/l10n/app_localizations_fr.dart | 5 +- lib/l10n/app_localizations_it.dart | 5 +- 3 files changed, 113 insertions(+), 31 deletions(-) diff --git a/lib/features/chat/widgets/assistant_message_widget.dart b/lib/features/chat/widgets/assistant_message_widget.dart index 5a2459d..fc54b2e 100644 --- a/lib/features/chat/widgets/assistant_message_widget.dart +++ b/lib/features/chat/widgets/assistant_message_widget.dart @@ -741,8 +741,10 @@ class _AssistantMessageWidgetState extends ConsumerState Padding( padding: const EdgeInsets.only(left: 4, bottom: 4), child: SizedBox( - height: 18, - child: _buildTypingDot(), + height: 22, + child: Platform.isIOS + ? _buildTypingPillBubble() + : _buildTypingEllipsis(), ), ), ], @@ -751,32 +753,114 @@ class _AssistantMessageWidgetState extends ConsumerState ); } - Widget _buildTypingDot() { + Widget _buildTypingEllipsis() { final min = AnimationValues.typingIndicatorScale; - return Container( - width: 14, - height: 14, - decoration: BoxDecoration( - color: context.conduitTheme.textSecondary.withValues(alpha: 0.6), - shape: BoxShape.circle, - ), - ) - .animate(onPlay: (controller) => controller.repeat()) - .scale( - duration: AnimationDuration.typingIndicator, - curve: AnimationCurves.typingIndicator, - begin: Offset(min, min), - end: const Offset(1, 1), - ) - .then(delay: AnimationDelay.typingDelay) - .scale( - duration: AnimationDuration.typingIndicator, - curve: AnimationCurves.typingIndicator, - begin: const Offset(1, 1), - end: Offset(min, min), - ); + final dotColor = context.conduitTheme.textSecondary.withValues(alpha: 0.75); + + const double dotSize = 6.0; + const double gap = Spacing.xs; // 4.0 + final d = AnimationDelay.typingDelay; + final d2 = Duration(milliseconds: d.inMilliseconds * 2); + + Widget dot(Duration delay) { + return Container( + width: dotSize, + height: dotSize, + decoration: BoxDecoration( + color: dotColor, + shape: BoxShape.circle, + ), + ) + .animate(onPlay: (controller) => controller.repeat()) + .then(delay: delay) + .scale( + duration: AnimationDuration.typingIndicator, + curve: AnimationCurves.typingIndicator, + begin: Offset(min, min), + end: const Offset(1, 1), + ) + .then(delay: AnimationDelay.typingDelay) + .scale( + duration: AnimationDuration.typingIndicator, + curve: AnimationCurves.typingIndicator, + begin: const Offset(1, 1), + end: Offset(min, min), + ); + } + + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + dot(Duration.zero), + const SizedBox(width: gap), + dot(d), + const SizedBox(width: gap), + dot(d2), + ], + ); } + Widget _buildTypingPillBubble() { + final min = AnimationValues.typingIndicatorScale; + + final bubbleColor = context.conduitTheme.surfaceContainerHighest; + final dotColor = context.conduitTheme.textSecondary.withValues(alpha: 0.75); + + const double dotSize = 6.0; + const double gap = Spacing.xs; // 4.0 + const double padV = 6.0; + const double padH = 10.0; + + final d = AnimationDelay.typingDelay; + final d2 = Duration(milliseconds: d.inMilliseconds * 2); + + Widget dot(Duration delay) { + return Container( + width: dotSize, + height: dotSize, + decoration: BoxDecoration( + color: dotColor, + shape: BoxShape.circle, + ), + ) + .animate(onPlay: (controller) => controller.repeat()) + .then(delay: delay) + .scale( + duration: AnimationDuration.typingIndicator, + curve: AnimationCurves.typingIndicator, + begin: Offset(min, min), + end: const Offset(1, 1), + ) + .then(delay: AnimationDelay.typingDelay) + .scale( + duration: AnimationDuration.typingIndicator, + curve: AnimationCurves.typingIndicator, + begin: const Offset(1, 1), + end: Offset(min, min), + ); + } + + return Container( + padding: const EdgeInsets.symmetric(horizontal: padH, vertical: padV), + decoration: BoxDecoration( + color: bubbleColor, + borderRadius: BorderRadius.circular(999), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + dot(Duration.zero), + const SizedBox(width: gap), + dot(d), + const SizedBox(width: gap), + dot(d2), + ], + ), + ); + } + + + Widget _buildActionButtons() { final isErrorMessage = widget.message.content.contains('⚠️') || diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 2abaff3..284a4d5 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -613,11 +613,10 @@ class AppLocalizationsFr extends AppLocalizations { 'Ce dossier et ses associations seront supprimés.'; @override - String get failedToDeleteFolder => - 'Échec de la suppression du dossier'; + String get failedToDeleteFolder => 'Échec de la suppression du dossier'; @override - String get aboutApp => 'À propos de l\'application'; + String get aboutApp => 'À propos de l’application'; @override String get aboutAppSubtitle => 'Informations et liens Conduit'; diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index 9f57f4b..8f17fcd 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -606,11 +606,10 @@ class AppLocalizationsIt extends AppLocalizations { 'Questa cartella e le sue associazioni verranno rimosse.'; @override - String get failedToDeleteFolder => - 'Impossibile eliminare la cartella'; + String get failedToDeleteFolder => 'Impossibile eliminare la cartella'; @override - String get aboutApp => 'Informazioni sull\'app'; + String get aboutApp => 'Informazioni sull’app'; @override String get aboutAppSubtitle => 'Informazioni e link di Conduit';