From 4ca1b1f89232655a676e3d7618eb1988c6c74308 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 19 May 2026 14:09:22 +0200 Subject: [PATCH 01/11] Improve slow mode UI --- migrations/redesign/localizations.md | 2 + migrations/redesign/message_composer.md | 12 +++- packages/stream_chat_flutter/CHANGELOG.md | 1 + .../message_composer_input_center.dart | 1 + .../message_composer_input_field.dart | 11 +++ .../message_composer_input_trailing.dart | 32 +++++++-- .../message_composer_leading.dart | 71 ++++++++++--------- .../lib/src/localization/translations.dart | 11 ++- .../message_input_placeholder.dart | 3 +- .../stream_message_composer.dart | 5 +- .../src/message_input/message_input_test.dart | 9 ++- .../stream_chat_localizations/CHANGELOG.md | 4 ++ .../example/lib/add_new_lang.dart | 2 +- .../lib/src/stream_chat_localizations_ca.dart | 2 +- .../lib/src/stream_chat_localizations_de.dart | 2 +- .../lib/src/stream_chat_localizations_en.dart | 2 +- .../lib/src/stream_chat_localizations_es.dart | 2 +- .../lib/src/stream_chat_localizations_fr.dart | 2 +- .../lib/src/stream_chat_localizations_hi.dart | 2 +- .../lib/src/stream_chat_localizations_it.dart | 2 +- .../lib/src/stream_chat_localizations_ja.dart | 2 +- .../lib/src/stream_chat_localizations_ko.dart | 2 +- .../lib/src/stream_chat_localizations_no.dart | 2 +- .../lib/src/stream_chat_localizations_pt.dart | 2 +- .../test/translations_test.dart | 2 +- 25 files changed, 131 insertions(+), 57 deletions(-) diff --git a/migrations/redesign/localizations.md b/migrations/redesign/localizations.md index ab090565e..3ec863b07 100644 --- a/migrations/redesign/localizations.md +++ b/migrations/redesign/localizations.md @@ -181,6 +181,7 @@ The following members were renamed. If you have overridden them in a custom `Tra |------------|------------|-------| | `String get questionsLabel` | `String questionLabel({bool isPlural = false})` | Now mirrors `optionLabel({bool isPlural})`. Pass `isPlural: true` to get the previous plural value, or call with no arguments for the singular "Question" label used in the poll results/options dialogs. | | `String get endVoteConfirmationText` | `String get endVoteConfirmationTitle` | Renamed to reflect that this string is the dialog title rather than body text; see also the new default value in [Changed Default String Values](#changed-default-string-values). | +| `String get slowModeOnLabel` | `String slowModeOnLabel(int cooldownTimeOut)` | Now takes the remaining cooldown in seconds so the placeholder can render a live countdown (default English: `'Slow mode, wait ${cooldownTimeOut}s\u2026'`). The composer text input is also disabled and the trailing send button shows the remaining seconds while slow mode is active. | Example migration: @@ -226,4 +227,5 @@ If your app overrides these in a `Translations` subclass, your custom values are - [ ] Add implementations for all 35 new abstract members listed above — the compiler will flag missing ones - [ ] Update the signature of any `questionsLabel` override to `questionLabel({bool isPlural = false})`, and replace any call to `translations.questionsLabel` with `translations.questionLabel(isPlural: true)` - [ ] Rename any `endVoteConfirmationText` override (and consumer) to `endVoteConfirmationTitle` +- [ ] Update the signature of any `slowModeOnLabel` override from `String get slowModeOnLabel` to `String slowModeOnLabel(int cooldownTimeOut)`, and update consumers to pass the cooldown seconds (e.g. `translations.slowModeOnLabel(cooldownTimeOut)`) - [ ] Review the changed default string values and decide whether to keep the new defaults or override them to preserve the old text diff --git a/migrations/redesign/message_composer.md b/migrations/redesign/message_composer.md index 7ba2c5501..1900782ff 100644 --- a/migrations/redesign/message_composer.md +++ b/migrations/redesign/message_composer.md @@ -252,6 +252,16 @@ The default builder now renders dedicated placeholders for Stream's built-in use The default builder no longer uses `addACommentOrSendLabel` ("Add a comment or send") when the input only has attachments and no text — it now falls back to `writeAMessageLabel` ("Send a message"), matching the empty/idle state. The `addACommentOrSendLabel` translation key is still part of the public `Translations` interface, so to restore the old behaviour override `placeholderBuilder` and map `AttachmentsPlaceholder()` to `translations.addACommentOrSendLabel` yourself. +### Behavior change: slow mode + +While slow mode is active for the current user, the composer is now visibly locked instead of just guarding the send call: + +- The text input is disabled (`enabled: false`) so the user cannot edit the field. +- The trailing send button is replaced by a disabled countdown button showing the remaining seconds (e.g. `9`). +- The placeholder shows a live countdown such as `Slow mode, wait 9s…` (English default), driven by `Translations.slowModeOnLabel(int cooldownTimeOut)`. The translation key changed signature — see the [Localizations migration guide](localizations.md). + +Once the cooldown reaches zero the input is automatically re-enabled and the regular send / microphone button returns. To restore the previous behaviour (editable input + normal send button while slow mode is active), supply your own `placeholderBuilder` and a custom trailing component via `streamChatComponentBuilders(messageComposerInputTrailing: ...)`. + ### Sealed-class state shape Each case carries the contextual data relevant to that input state. Pattern-match on these fields in your `placeholderBuilder` to render rich, state-aware placeholders. @@ -272,7 +282,7 @@ StreamMessageComposer( final translations = context.translations; return switch (placeholder) { SlowModePlaceholder(:final cooldownTimeOut) => - 'Slow mode on – ${cooldownTimeOut}s left', + translations.slowModeOnLabel(cooldownTimeOut), CommandPlaceholder(command: 'giphy') => translations.searchGifLabel, CommandPlaceholder(command: 'mute' || 'unmute' || 'ban' || 'unban') => translations.commandUsernameLabel, diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 7d289cb76..2325110bb 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -27,6 +27,7 @@ - Redesigned `StreamPollOptionsSheetThemeData`, `StreamPollResultsSheetThemeData`, `StreamPollOptionVotesSheetThemeData` and `StreamPollCommentsSheetThemeData` — see [`migrations/redesign/attachments_and_polls.md`](../../migrations/redesign/attachments_and_polls.md). - Renamed `Translations.questionsLabel` getter → `questionLabel({bool isPlural = false})` method. - Renamed `Translations.endVoteConfirmationText` → `endVoteConfirmationTitle`; English default changed to `'End This Poll?'`. +- Renamed `Translations.slowModeOnLabel` getter → `slowModeOnLabel(int cooldownTimeOut)` method; English default changed from `'Slow mode ON'` to `'Slow mode, wait ${cooldownTimeOut}s\u2026'`. While slow mode is active for the current user the composer text input is now disabled and the trailing send button is replaced by a disabled countdown button showing the remaining seconds, matching the redesigned Figma. See [`migrations/redesign/message_composer.md`](../../migrations/redesign/message_composer.md). - Reworded `Translations.endVoteLabel` English default to `'End Poll'`. - Removed `AttachmentButton`, `StreamQuotedMessageWidget`, `EditMessageSheet`, `StreamMessageSendButton` and `DesktopReactionsBuilder`. - Removed `StreamChannelGridView`, `StreamChannelGridTile` and `StreamMessageSearchGridView`. diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart index 53a02c395..7ddd190eb 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart @@ -83,6 +83,7 @@ class DefaultStreamMessageComposerInputCenter extends StatelessWidget { textCapitalization: props.textCapitalization, autofocus: props.autofocus, autocorrect: props.autocorrect, + enabled: !controller.isSlowModeActive, ), if (props.canAlsoSendToChannel) DmCheckboxListTile( diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_field.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_field.dart index 3832e447f..c7f9fec32 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_field.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_field.dart @@ -21,6 +21,7 @@ class StreamMessageComposerInputField extends StatelessWidget { this.textCapitalization = TextCapitalization.sentences, this.autofocus = false, this.autocorrect = true, + this.enabled = true, }); /// The controller for the text field. @@ -53,6 +54,15 @@ class StreamMessageComposerInputField extends StatelessWidget { /// Whether to enable autocorrect. final bool autocorrect; + /// Whether the text field is enabled. + /// + /// When false the text field cannot be edited and the placeholder remains + /// visible regardless of the underlying [controller] text. Used by the + /// composer to lock input while slow mode is active. + /// + /// Defaults to true. + final bool enabled; + @override Widget build(BuildContext context) { final spacing = context.streamSpacing; @@ -85,6 +95,7 @@ class StreamMessageComposerInputField extends StatelessWidget { child: TextField( controller: controller, focusNode: focusNode, + enabled: enabled, textInputAction: textInputAction, keyboardType: keyboardType, textCapitalization: textCapitalization, diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 068124531..5a7aea27e 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -41,6 +41,17 @@ class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { return ValueListenableBuilder( valueListenable: _controller, builder: (context, value, child) { + if (props.isAudioRecordingFlowLocked || props.isAudioRecordingFlowStopped) { + return const SizedBox.shrink(); + } + + if (_controller.isSlowModeActive) { + return _SlowModeCountdownButton( + key: _slowModeKey, + cooldownTimeOut: _controller.cooldownTimeOut, + ); + } + final hasText = _controller.text.trim().isNotEmpty; final hasContent = hasText || _controller.attachments.isNotEmpty; final isEditing = _controller.isEditing; @@ -60,10 +71,6 @@ class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { final isEnabled = (!isEditing && !hasCommand) || hasContent; - if (props.isAudioRecordingFlowLocked || props.isAudioRecordingFlowStopped) { - return const SizedBox.shrink(); - } - final voiceRecordingCallback = props.voiceRecordingCallback; if (buttonState == _ButtonState.send || buttonState == _ButtonState.edit || @@ -98,6 +105,22 @@ enum _ButtonState { voiceRecordingActive, } +class _SlowModeCountdownButton extends StatelessWidget { + const _SlowModeCountdownButton({super.key, required this.cooldownTimeOut}); + + final int cooldownTimeOut; + + @override + Widget build(BuildContext context) { + return StreamButton.icon( + icon: Text('$cooldownTimeOut'), + style: StreamButtonStyle.secondary, + size: StreamButtonSize.small, + onPressed: null, + ); + } +} + class _VoiceRecordingButton extends StatelessWidget { const _VoiceRecordingButton({ required this.voiceRecordingCallback, @@ -142,3 +165,4 @@ class _VoiceRecordingButton extends StatelessWidget { final _sendKey = UniqueKey(); final _microphoneKey = UniqueKey(); +final _slowModeKey = UniqueKey(); diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 50113af72..a0944e070 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -35,39 +35,46 @@ class DefaultStreamMessageComposerLeading extends StatelessWidget { Widget build(BuildContext context) { // 45 degrees = 0.125 turns const closedRotation = 0.125; - final showButton = - props.onAttachmentButtonPressed != null && - !props.isAudioRecordingFlowActive && - props.controller.message.command == null; - return AnimatedOpacity( - opacity: showButton ? 1.0 : 0.0, - duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, - curve: Curves.easeInQuint, - child: AnimatedSize( - duration: const Duration(milliseconds: 200), - alignment: Alignment.bottomCenter, - child: Row( - children: [ - if (showButton) ...[ - AnimatedRotation( - turns: props.isPickerOpen ? closedRotation : 0, - duration: const Duration(milliseconds: 150), - curve: Curves.easeOut, - child: StreamButton.icon( - icon: Icon(context.streamIcons.plus), - style: StreamButtonStyle.secondary, - type: StreamButtonType.outline, - size: StreamButtonSize.large, - isFloating: props.isFloating, - onPressed: () => props.onAttachmentButtonPressed?.call(), - ), - ), - SizedBox(width: context.streamSpacing.xs), - ], - ], - ), - ), + return ValueListenableBuilder( + valueListenable: props.controller, + builder: (context, value, child) { + final isSlowModeActive = props.controller.isSlowModeActive; + final showButton = + props.onAttachmentButtonPressed != null && + !props.isAudioRecordingFlowActive && + props.controller.message.command == null; + + return AnimatedOpacity( + opacity: showButton ? 1.0 : 0.0, + duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, + curve: Curves.easeInQuint, + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomCenter, + child: Row( + children: [ + if (showButton) ...[ + AnimatedRotation( + turns: props.isPickerOpen ? closedRotation : 0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + child: StreamButton.icon( + icon: Icon(context.streamIcons.plus), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + isFloating: props.isFloating, + onPressed: isSlowModeActive ? null : () => props.onAttachmentButtonPressed?.call(), + ), + ), + SizedBox(width: context.streamSpacing.xs), + ], + ], + ), + ), + ); + }, ); } } diff --git a/packages/stream_chat_flutter/lib/src/localization/translations.dart b/packages/stream_chat_flutter/lib/src/localization/translations.dart index 34ad68c91..81bee7d14 100644 --- a/packages/stream_chat_flutter/lib/src/localization/translations.dart +++ b/packages/stream_chat_flutter/lib/src/localization/translations.dart @@ -116,8 +116,13 @@ abstract class Translations { /// The label for write a message in [StreamMessageComposer] String get writeAMessageLabel; - /// The label for slow mode enabled in [StreamMessageComposer] - String get slowModeOnLabel; + /// The placeholder shown in [StreamMessageComposer] while slow mode is + /// active for the current user. + /// + /// [cooldownTimeOut] is the number of seconds remaining before the user + /// can send another message. Defaults to `'Slow mode, wait ${cooldownTimeOut}s\u2026'` + /// which renders as e.g. "Slow mode, wait 9s…". + String slowModeOnLabel(int cooldownTimeOut); /// The placeholder shown in the composer when a user-target command (for /// example `/mute`, `/unmute`, `/ban`, `/unban`) is active. @@ -1106,7 +1111,7 @@ class DefaultTranslations implements Translations { String replyToUserLabel(String userName) => 'Reply to $userName'; @override - String get slowModeOnLabel => 'Slow mode ON'; + String slowModeOnLabel(int cooldownTimeOut) => 'Slow mode, wait ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_flutter/lib/src/message_input/message_input_placeholder.dart b/packages/stream_chat_flutter/lib/src/message_input/message_input_placeholder.dart index 5563ccb89..b2896beba 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/message_input_placeholder.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/message_input_placeholder.dart @@ -26,7 +26,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// ) { /// final translations = context.translations; /// return switch (placeholder) { -/// SlowModePlaceholder() => translations.slowModeOnLabel, +/// SlowModePlaceholder(:final cooldownTimeOut) => +/// translations.slowModeOnLabel(cooldownTimeOut), /// CommandPlaceholder(command: 'giphy') => translations.searchGifLabel, /// CommandPlaceholder(command: 'mute' || 'unmute' || 'ban' || 'unban') => /// translations.commandUsernameLabel, diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart index 2f2f93346..021358253 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_message_composer.dart @@ -332,7 +332,8 @@ class MessageComposerProps { /// placeholderBuilder: (context, placeholder) { /// final translations = context.translations; /// return switch (placeholder) { - /// SlowModePlaceholder() => translations.slowModeOnLabel, + /// SlowModePlaceholder(:final cooldownTimeOut) => + /// translations.slowModeOnLabel(cooldownTimeOut), /// CommandPlaceholder(command: 'weather') => 'Type a city name', /// CommandPlaceholder() => translations.writeAMessageLabel, /// AttachmentsPlaceholder() => translations.addACommentOrSendLabel, @@ -440,7 +441,7 @@ class MessageComposerProps { ) { final translations = context.translations; return switch (placeholder) { - SlowModePlaceholder() => translations.slowModeOnLabel, + SlowModePlaceholder(:final cooldownTimeOut) => translations.slowModeOnLabel(cooldownTimeOut), CommandPlaceholder(command: 'giphy') => translations.searchGifLabel, CommandPlaceholder(command: 'mute' || 'unmute' || 'ban' || 'unban') => translations.commandUsernameLabel, CommandPlaceholder() || AttachmentsPlaceholder() || WriteMessagePlaceholder() => translations.writeAMessageLabel, diff --git a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart index 2f3b787f2..1b3923700 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_input_test.dart @@ -106,7 +106,14 @@ void main() { // wait for the initial state to be rendered. await tester.pumpAndSettle(); - expect(find.text('Slow mode ON'), findsOneWidget); + expect(find.text('Slow mode, wait 10s\u2026'), findsOneWidget); + + // The text field is locked while slow mode is active. + final textField = tester.widget(find.byType(TextField)); + expect(textField.enabled, isFalse); + + // The trailing button shows the remaining cooldown instead of send / mic. + expect(find.text('10'), findsOneWidget); }, ); diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index dc1736422..aceb90f99 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -9,6 +9,10 @@ method across all supported locales. - Renamed `endVoteConfirmationText` → `endVoteConfirmationTitle` across all supported locales; English default changed to `'End This Poll?'`. +- Renamed `slowModeOnLabel` getter → `slowModeOnLabel(int cooldownTimeOut)` + method across all supported locales. The default now renders a live + countdown (English default: `'Slow mode ON'` → `'Slow mode, wait + ${cooldownTimeOut}s\u2026'`). ✅ Added diff --git a/packages/stream_chat_localizations/example/lib/add_new_lang.dart b/packages/stream_chat_localizations/example/lib/add_new_lang.dart index 1b9bf4264..bb796fee2 100644 --- a/packages/stream_chat_localizations/example/lib/add_new_lang.dart +++ b/packages/stream_chat_localizations/example/lib/add_new_lang.dart @@ -406,7 +406,7 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations { String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override - String get slowModeOnLabel => 'Slow mode ON'; + String slowModeOnLabel(int cooldownTimeOut) => 'Slow mode, wait ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart index 3b8b467d4..8ff95df68 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart @@ -388,7 +388,7 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations { String get viewLibrary => 'Veure llibreria'; @override - String get slowModeOnLabel => 'Mode lent activat'; + String slowModeOnLabel(int cooldownTimeOut) => 'Mode lent, espera ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart index 0761fb974..2982e226e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_de.dart @@ -380,7 +380,7 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations { String attachmentLimitExceedError(int limit) => 'Dateigröße überschritten, Grenze: $limit'; @override - String get slowModeOnLabel => 'Langsamer Modus: EIN'; + String slowModeOnLabel(int cooldownTimeOut) => 'Langsamer Modus, warte ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart index bb53b34fa..ec178210d 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart @@ -384,7 +384,7 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations { String attachmentLimitExceedError(int limit) => 'Attachment limit exceeded, limit: $limit'; @override - String get slowModeOnLabel => 'Slow mode ON'; + String slowModeOnLabel(int cooldownTimeOut) => 'Slow mode, wait ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart index 2d971b1a2..5c8509d96 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart @@ -391,7 +391,7 @@ No es posible añadir más de $limit archivos adjuntos String get viewLibrary => 'Ver Librería'; @override - String get slowModeOnLabel => 'Modo lento activado'; + String slowModeOnLabel(int cooldownTimeOut) => 'Modo lento, espera ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart index b6ace6fda..1b7dcf4bb 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart @@ -391,7 +391,7 @@ Limite de pièces jointes dépassée : il n'est pas possible d'ajouter plus de $ String get viewLibrary => 'Voir la bibliothèque'; @override - String get slowModeOnLabel => 'Mode lent activé'; + String slowModeOnLabel(int cooldownTimeOut) => 'Mode lent, attendez ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart index 199faf2bd..0ec85b93a 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart @@ -389,7 +389,7 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations { String get viewLibrary => 'पुस्तकालय देखिये'; @override - String get slowModeOnLabel => 'स्लो मोड चालू'; + String slowModeOnLabel(int cooldownTimeOut) => 'स्लो मोड, $cooldownTimeOut सेकंड प्रतीक्षा करें\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart index f5f98a969..f169fae6b 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart @@ -394,7 +394,7 @@ Attenzione: il limite massimo di $limit file è stato superato. String get viewLibrary => 'Vedi la biblioteca'; @override - String get slowModeOnLabel => 'Slowmode attiva'; + String slowModeOnLabel(int cooldownTimeOut) => 'Modalità lenta, attendi ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart index 817cc2ea0..d87b19649 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart @@ -368,7 +368,7 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations { String get replyToMessageLabel => 'メッセージに返信'; @override - String get slowModeOnLabel => 'スローモードオン'; + String slowModeOnLabel(int cooldownTimeOut) => 'スローモード、$cooldownTimeOut秒お待ちください\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart index 197ce29a6..6bec8e741 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart @@ -371,7 +371,7 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations { String get replyToMessageLabel => '메시지에 회신합니다.'; @override - String get slowModeOnLabel => '슬로모드 켜짐'; + String slowModeOnLabel(int cooldownTimeOut) => '슬로모드, $cooldownTimeOut초만 기다려 주세요\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart index 87ccc9a32..181190e9c 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_no.dart @@ -380,7 +380,7 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations { String attachmentLimitExceedError(int limit) => 'Antall vedlegg oversteget, maks antall: $limit'; @override - String get slowModeOnLabel => 'Sakte modus PÅ'; + String slowModeOnLabel(int cooldownTimeOut) => 'Sakte modus, vent ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart index 1f7f22350..92b5246b4 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -384,7 +384,7 @@ Não é possível adicionar mais de $limit arquivos de uma vez '''; @override - String get slowModeOnLabel => 'Modo lento ativado'; + String slowModeOnLabel(int cooldownTimeOut) => 'Modo lento, aguarde ${cooldownTimeOut}s\u2026'; @override String get commandUsernameLabel => '@username'; diff --git a/packages/stream_chat_localizations/test/translations_test.dart b/packages/stream_chat_localizations/test/translations_test.dart index 3efef3634..27431d8e1 100644 --- a/packages/stream_chat_localizations/test/translations_test.dart +++ b/packages/stream_chat_localizations/test/translations_test.dart @@ -196,7 +196,7 @@ void main() { localizations.galleryPaginationText(currentPage: 1, totalPages: 2), isNotNull, ); - expect(localizations.slowModeOnLabel, isNotNull); + expect(localizations.slowModeOnLabel(10), isNotNull); expect(localizations.linkDisabledDetails, isNotNull); expect(localizations.linkDisabledError, isNotNull); expect(localizations.sendMessagePermissionError, isNotNull); From 1b0a31cc6672e1953b2280565f2cbc46269aef9d Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 19 May 2026 14:22:24 +0200 Subject: [PATCH 02/11] disabled color for border of composer input --- .../message_composer_input.dart | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart index 6276cf98d..51db68b3a 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart @@ -47,19 +47,27 @@ class DefaultStreamMessageComposerInput extends StatelessWidget { Widget build(BuildContext context) { final isFloating = props.isFloating; - return Container( - clipBehavior: Clip.antiAlias, - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.all(context.streamRadius.xxxl), - border: Border.all( - color: context.streamColorScheme.borderDefault, - ), - ), - decoration: BoxDecoration( - color: context.streamColorScheme.backgroundElevation1, - borderRadius: BorderRadius.all(context.streamRadius.xxxl), - boxShadow: isFloating ? context.streamBoxShadow.elevation3 : null, - ), + return ValueListenableBuilder( + valueListenable: props.controller, + builder: (context, value, child) { + final borderColor = props.controller.isSlowModeActive + ? context.streamColorScheme.borderDisabled + : context.streamColorScheme.borderDefault; + + return Container( + clipBehavior: Clip.antiAlias, + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.all(context.streamRadius.xxxl), + border: Border.all(color: borderColor), + ), + decoration: BoxDecoration( + color: context.streamColorScheme.backgroundElevation1, + borderRadius: BorderRadius.all(context.streamRadius.xxxl), + boxShadow: isFloating ? context.streamBoxShadow.elevation3 : null, + ), + child: child, + ); + }, child: Column( mainAxisSize: MainAxisSize.min, children: [ From 5ac861ba8978164ae2952c68dea9130d3843e912 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 19 May 2026 14:33:07 +0200 Subject: [PATCH 03/11] Remove nested builders --- .../message_composer_component_props.dart | 26 +++++ .../message_composer_input.dart | 34 +++---- .../message_composer_input_center.dart | 2 +- .../message_composer_input_trailing.dart | 4 +- .../message_composer_leading.dart | 73 +++++++------- .../stream_chat_message_input.dart | 98 ++++++++++--------- 6 files changed, 128 insertions(+), 109 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart index c9acbb2a6..5a76b977b 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_component_props.dart @@ -48,6 +48,7 @@ class MessageComposerComponentProps { this.currentUserId, required this.audioRecorderState, this.onQuotedMessageCleared, + this.cooldownTimeOut, }); /// The controller for the message composer component. @@ -80,6 +81,17 @@ class MessageComposerComponentProps { /// Callback for when the quoted message is cleared. final VoidCallback? onQuotedMessageCleared; + /// Remaining slow-mode cooldown in seconds, or `null` when slow mode is not + /// active for the current user. + /// + /// Non-null (even if zero is never emitted) means the composer should be + /// locked: text input disabled, attachment button disabled, border dimmed, + /// and the send button replaced by a countdown indicator. + final int? cooldownTimeOut; + + /// Whether slow mode is currently active for the current user. + bool get isSlowModeActive => cooldownTimeOut != null; + /// Whether the audio recording flow is active. bool get isAudioRecordingFlowActive => audioRecorderState is RecordStateRecording || isAudioRecordingFlowStopped; @@ -103,6 +115,7 @@ class MessageComposerLeadingProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, }) : super(); /// Creates a new instance of [MessageComposerLeadingProps] from a [MessageComposerComponentProps]. @@ -118,6 +131,7 @@ class MessageComposerLeadingProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, ); } } @@ -135,6 +149,7 @@ class MessageComposerTrailingProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, }) : super(); /// Creates a new instance of [MessageComposerTrailingProps] from a [MessageComposerComponentProps]. @@ -150,6 +165,7 @@ class MessageComposerTrailingProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, ); } } @@ -167,6 +183,7 @@ class MessageComposerInputProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, this.placeholder, this.textInputAction, this.keyboardType, @@ -205,6 +222,7 @@ class MessageComposerInputProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, placeholder: placeholder, textInputAction: textInputAction, keyboardType: keyboardType, @@ -262,6 +280,7 @@ class MessageComposerInputCenterProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, this.placeholder, this.textInputAction, this.keyboardType, @@ -288,6 +307,7 @@ class MessageComposerInputCenterProps extends MessageComposerComponentProps { currentUserId: inputProps.currentUserId, audioRecorderState: inputProps.audioRecorderState, onQuotedMessageCleared: inputProps.onQuotedMessageCleared, + cooldownTimeOut: inputProps.cooldownTimeOut, placeholder: inputProps.placeholder, textInputAction: inputProps.textInputAction, keyboardType: inputProps.keyboardType, @@ -345,6 +365,7 @@ class MessageComposerInputLeadingProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, }) : super(); /// Creates a new instance of [MessageComposerInputLeadingProps] from a [MessageComposerComponentProps]. @@ -360,6 +381,7 @@ class MessageComposerInputLeadingProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, ); } } @@ -377,6 +399,7 @@ class MessageComposerInputHeaderProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, }) : super(); /// Creates a new instance of [MessageComposerInputHeaderProps] from a [MessageComposerComponentProps]. @@ -392,6 +415,7 @@ class MessageComposerInputHeaderProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, ); } } @@ -409,6 +433,7 @@ class MessageComposerInputTrailingProps extends MessageComposerComponentProps { required super.currentUserId, required super.audioRecorderState, required super.onQuotedMessageCleared, + required super.cooldownTimeOut, }) : super(); /// Creates a new instance of [MessageComposerInputTrailingProps] from a [MessageComposerComponentProps]. @@ -424,6 +449,7 @@ class MessageComposerInputTrailingProps extends MessageComposerComponentProps { currentUserId: props.currentUserId, audioRecorderState: props.audioRecorderState, onQuotedMessageCleared: props.onQuotedMessageCleared, + cooldownTimeOut: props.cooldownTimeOut, ); } } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart index 51db68b3a..eb146887e 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart @@ -46,28 +46,20 @@ class DefaultStreamMessageComposerInput extends StatelessWidget { @override Widget build(BuildContext context) { final isFloating = props.isFloating; + final borderColor = + props.isSlowModeActive ? context.streamColorScheme.borderDisabled : context.streamColorScheme.borderDefault; - return ValueListenableBuilder( - valueListenable: props.controller, - builder: (context, value, child) { - final borderColor = props.controller.isSlowModeActive - ? context.streamColorScheme.borderDisabled - : context.streamColorScheme.borderDefault; - - return Container( - clipBehavior: Clip.antiAlias, - foregroundDecoration: BoxDecoration( - borderRadius: BorderRadius.all(context.streamRadius.xxxl), - border: Border.all(color: borderColor), - ), - decoration: BoxDecoration( - color: context.streamColorScheme.backgroundElevation1, - borderRadius: BorderRadius.all(context.streamRadius.xxxl), - boxShadow: isFloating ? context.streamBoxShadow.elevation3 : null, - ), - child: child, - ); - }, + return Container( + clipBehavior: Clip.antiAlias, + foregroundDecoration: BoxDecoration( + borderRadius: BorderRadius.all(context.streamRadius.xxxl), + border: Border.all(color: borderColor), + ), + decoration: BoxDecoration( + color: context.streamColorScheme.backgroundElevation1, + borderRadius: BorderRadius.all(context.streamRadius.xxxl), + boxShadow: isFloating ? context.streamBoxShadow.elevation3 : null, + ), child: Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart index 7ddd190eb..a52f36d0c 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_center.dart @@ -83,7 +83,7 @@ class DefaultStreamMessageComposerInputCenter extends StatelessWidget { textCapitalization: props.textCapitalization, autofocus: props.autofocus, autocorrect: props.autocorrect, - enabled: !controller.isSlowModeActive, + enabled: !props.isSlowModeActive, ), if (props.canAlsoSendToChannel) DmCheckboxListTile( diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 5a7aea27e..d7eb10284 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -45,10 +45,10 @@ class DefaultStreamMessageComposerInputTrailing extends StatelessWidget { return const SizedBox.shrink(); } - if (_controller.isSlowModeActive) { + if (props.isSlowModeActive) { return _SlowModeCountdownButton( key: _slowModeKey, - cooldownTimeOut: _controller.cooldownTimeOut, + cooldownTimeOut: props.cooldownTimeOut!, ); } diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index a0944e070..5916f3931 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -1,4 +1,4 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// A widget that shows the leading of the message composer. @@ -35,46 +35,39 @@ class DefaultStreamMessageComposerLeading extends StatelessWidget { Widget build(BuildContext context) { // 45 degrees = 0.125 turns const closedRotation = 0.125; + final showButton = + props.onAttachmentButtonPressed != null && + !props.isAudioRecordingFlowActive && + props.controller.message.command == null; - return ValueListenableBuilder( - valueListenable: props.controller, - builder: (context, value, child) { - final isSlowModeActive = props.controller.isSlowModeActive; - final showButton = - props.onAttachmentButtonPressed != null && - !props.isAudioRecordingFlowActive && - props.controller.message.command == null; - - return AnimatedOpacity( - opacity: showButton ? 1.0 : 0.0, - duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, - curve: Curves.easeInQuint, - child: AnimatedSize( - duration: const Duration(milliseconds: 200), - alignment: Alignment.bottomCenter, - child: Row( - children: [ - if (showButton) ...[ - AnimatedRotation( - turns: props.isPickerOpen ? closedRotation : 0, - duration: const Duration(milliseconds: 150), - curve: Curves.easeOut, - child: StreamButton.icon( - icon: Icon(context.streamIcons.plus), - style: StreamButtonStyle.secondary, - type: StreamButtonType.outline, - size: StreamButtonSize.large, - isFloating: props.isFloating, - onPressed: isSlowModeActive ? null : () => props.onAttachmentButtonPressed?.call(), - ), - ), - SizedBox(width: context.streamSpacing.xs), - ], - ], - ), - ), - ); - }, + return AnimatedOpacity( + opacity: showButton ? 1.0 : 0.0, + duration: showButton ? const Duration(milliseconds: 200) : Duration.zero, + curve: Curves.easeInQuint, + child: AnimatedSize( + duration: const Duration(milliseconds: 200), + alignment: Alignment.bottomCenter, + child: Row( + children: [ + if (showButton) ...[ + AnimatedRotation( + turns: props.isPickerOpen ? closedRotation : 0, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOut, + child: StreamButton.icon( + icon: Icon(context.streamIcons.plus), + style: StreamButtonStyle.secondary, + type: StreamButtonType.outline, + size: StreamButtonSize.large, + isFloating: props.isFloating, + onPressed: props.isSlowModeActive ? null : () => props.onAttachmentButtonPressed?.call(), + ), + ), + SizedBox(width: context.streamSpacing.xs), + ], + ], + ), + ), ); } } diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart index f9e59ead7..6c00be23e 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart @@ -202,56 +202,64 @@ class _StreamChatMessageInputContent extends StatelessWidget { @override Widget build(BuildContext context) { - final componentProps = MessageComposerComponentProps( - controller: inputController, - isFloating: widget.isFloating, - currentUserId: widget.currentUserId, - onSendPressed: widget.onSendPressed, - voiceRecordingCallback: _createVoiceRecordingCallback(context), - onAttachmentButtonPressed: widget.onAttachmentButtonPressed, - isPickerOpen: widget.isPickerOpen, - audioRecorderState: audioRecorderState, - focusNode: widget.focusNode, - onQuotedMessageCleared: widget.onQuotedMessageCleared, - ); + final spacing = context.streamSpacing; - final inputProps = MessageComposerInputProps.from( - componentProps, - placeholder: widget.placeholder, - textInputAction: widget.textInputAction, - keyboardType: widget.keyboardType, - textCapitalization: widget.textCapitalization, - autofocus: widget.autofocus, - autocorrect: widget.autocorrect, - canAlsoSendToChannel: widget.canAlsoSendToChannel, - audioRecorderController: widget.audioRecorderController, - feedback: widget.feedback, - sendVoiceRecordingAutomatically: widget.sendVoiceRecordingAutomatically, - ); + return ValueListenableBuilder( + valueListenable: inputController, + builder: (context, value, child) { + final cooldownTimeOut = inputController.isSlowModeActive ? inputController.cooldownTimeOut : null; + + final componentProps = MessageComposerComponentProps( + controller: inputController, + isFloating: widget.isFloating, + currentUserId: widget.currentUserId, + onSendPressed: widget.onSendPressed, + voiceRecordingCallback: _createVoiceRecordingCallback(context), + onAttachmentButtonPressed: widget.onAttachmentButtonPressed, + isPickerOpen: widget.isPickerOpen, + audioRecorderState: audioRecorderState, + focusNode: widget.focusNode, + onQuotedMessageCleared: widget.onQuotedMessageCleared, + cooldownTimeOut: cooldownTimeOut, + ); - final spacing = context.streamSpacing; + final inputProps = MessageComposerInputProps.from( + componentProps, + placeholder: widget.placeholder, + textInputAction: widget.textInputAction, + keyboardType: widget.keyboardType, + textCapitalization: widget.textCapitalization, + autofocus: widget.autofocus, + autocorrect: widget.autocorrect, + canAlsoSendToChannel: widget.canAlsoSendToChannel, + audioRecorderController: widget.audioRecorderController, + feedback: widget.feedback, + sendVoiceRecordingAutomatically: widget.sendVoiceRecordingAutomatically, + ); - return Container( - padding: EdgeInsets.only(top: spacing.md), - decoration: widget.isFloating - ? null - : BoxDecoration( - border: Border( - top: BorderSide(color: context.streamColorScheme.borderDefault), + return Container( + padding: EdgeInsets.only(top: spacing.md), + decoration: widget.isFloating + ? null + : BoxDecoration( + border: Border( + top: BorderSide(color: context.streamColorScheme.borderDefault), + ), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + SizedBox(width: spacing.md), + StreamMessageComposerLeading(props: componentProps), + Expanded( + child: StreamMessageComposerInput(props: inputProps), ), - ), - child: Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - SizedBox(width: spacing.md), - StreamMessageComposerLeading(props: componentProps), - Expanded( - child: StreamMessageComposerInput(props: inputProps), + StreamMessageComposerTrailing(props: componentProps), + SizedBox(width: spacing.md), + ], ), - StreamMessageComposerTrailing(props: componentProps), - SizedBox(width: spacing.md), - ], - ), + ); + }, ); } From 0ff359948c633481f852136c40fe5093ef255b82 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Tue, 19 May 2026 14:34:45 +0200 Subject: [PATCH 04/11] remove default value --- .../message_composer/message_composer_input_trailing.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index d7eb10284..5814e1636 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -116,7 +116,6 @@ class _SlowModeCountdownButton extends StatelessWidget { icon: Text('$cooldownTimeOut'), style: StreamButtonStyle.secondary, size: StreamButtonSize.small, - onPressed: null, ); } } From bacc46ee18ff13156e8175ffc6be957d961fe8de Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 20 May 2026 12:58:21 +0200 Subject: [PATCH 05/11] use constant keys --- .../message_composer/message_composer_input_trailing.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart index 5814e1636..8b880ac8a 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input_trailing.dart @@ -162,6 +162,6 @@ class _VoiceRecordingButton extends StatelessWidget { } } -final _sendKey = UniqueKey(); -final _microphoneKey = UniqueKey(); -final _slowModeKey = UniqueKey(); +const _sendKey = ValueKey('send_key'); +const _microphoneKey = ValueKey('microphone_key'); +const _slowModeKey = ValueKey('slow_mode_key'); From 683337c59a5c68b0fb46d57414f21c59888f4b6c Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 20 May 2026 13:07:41 +0200 Subject: [PATCH 06/11] formatting --- .../components/message_composer/message_composer_input.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart index eb146887e..a84afc98b 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_input.dart @@ -46,8 +46,9 @@ class DefaultStreamMessageComposerInput extends StatelessWidget { @override Widget build(BuildContext context) { final isFloating = props.isFloating; - final borderColor = - props.isSlowModeActive ? context.streamColorScheme.borderDisabled : context.streamColorScheme.borderDefault; + final borderColor = props.isSlowModeActive + ? context.streamColorScheme.borderDisabled + : context.streamColorScheme.borderDefault; return Container( clipBehavior: Clip.antiAlias, From 325af7a07b799e9cc39ce4c6489b02ec1d35b26d Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 20 May 2026 13:26:53 +0200 Subject: [PATCH 07/11] Added slow mode snapshot tests --- .../macos/message_composer_slow_mode.png | Bin 0 -> 6745 bytes .../goldens/macos/message_input.png | Bin 7118 -> 7120 bytes .../macos/message_input_change_position.png | Bin 7157 -> 7166 bytes .../macos/message_input_custom_send_icon.png | Bin 6877 -> 6878 bytes .../macos/message_input_quoted_message.png | Bin 12851 -> 12857 bytes .../macos/stream_message_composer_default.png | Bin 7118 -> 7120 bytes .../stream_message_composer_test.dart | 24 +++++ .../message_composer_slow_mode_test.dart | 83 ++++++++++++++++++ 8 files changed, 107 insertions(+) create mode 100644 docs/docs_screenshots/test/message_input/goldens/macos/message_composer_slow_mode.png create mode 100644 packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_composer_slow_mode.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_composer_slow_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..5ace9ec650df56848b822f7cc550728545b7d75f GIT binary patch literal 6745 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 literal 0 HcmV?d00001 diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input.png index 15cd1ebb2c3ce86a14219819d9de97950f50a0ee..a0c3b8a526222bdd701f53a2e8394a6effde4b97 100644 GIT binary patch delta 5666 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 IM6N<$g2{MLjsO4v delta 5611 zcmVr+i|MAxrN!ui?x~yhf?j76M1_Q=`DNf)Z zlmrNba2$lk9R~-23qO8=Kp+H2fP=eGe#8fXgj^tCiVd~_8yolDFXbi3#X+Ntg_}p+4k3ak{;DygvMaAi-dWKqy2o7$h8);q-+#8>!VQ613Wm zd`e8v4y$Y$(#bk(4i`?B2ZzgzQLiV(WI(M}bp*LqqXAHYzAf;>;Rvlx7cP&FWTT#B z3ZtIXWaChMgf0b>%NiVii(0K3>O&Pm^g}otArK4_2r4&f{y-3y+l$v9z^G5cXwZ{t zGN9FJhUEB$B;2ibCk-uDQj7*nMm@GqP7VJ5|7h{<;QbPSOQE!I|AYz~r*da~0~Nk|wrRWBjL5by_r zG+AsoTwXHMQb*Ph*P>e;`OvUN(t?aN{@N;T{f0D6OhvL{=ImgMJ`) z)xqKN(9mpUOwkBbeXaNOk=x^=s=kTb%(P)y`xinCA9ja}`erMIBeF2)k_L2N0~+rB zK#=2Ab&M$-G3X@H|gYyq1goqD+E{~V$x@K~-2KI(FATY}Py7aqD#~N_j7gI#2pLVEJA+x%Cw9E=^OfuHdi_kDG=??nx8ZcU znLl?1b7oEif=YhT3$`MIh8rHrZ9c- zlaUA~2R&ml^JY&alfMWje{*I|VcO)eBqp5ry=%7E*tYWkyZ0Zas9*$hXHH>kaZ#sl zl;xGRtlhYS>e>d1M;0(=))dB$?mYgHz17L5Uw%tvbv;=bX`i}wvK~4u0ep8Dt~uw-%XCSJ*rVud*-rE1lTSVw%?2zs2gxSG zfV6L4vgBRVYBm3O=j}1yb|@6a;r3uo>3(WHt6iBcFPeD}kCax>*kr-y^D}Au=#DPN zvt~?U@uCaRx3!&jf4bZ}{kyj~c=$Le$wta5YT2>-AV!0p(M8I*IDh}_EA|{X!j>KT z(P%U@x7gUZ_Yh8(o9R==0pRuec>K4oQC3kynmHMJtCNk}_5v_|%t*$M8F|Lzv{>yt z@!QuatE?p}GmX0XCN^x{LogU*()i*oKi`L+e$Ac(N7%FfTnGlej=K70j+IrhZRY{j ztlx&u=f~sqv3uWPj+9n#(RnjF-n(keX8!Ww7g%krBxto9IDDK9TlTQAr>6x29o@(JlD9%z^&wRP}#OwR3mHN3cNvXvbq{ID}!}t;K z#U2QD)1pzMRt6b6lW;hU!{v^8IN+!gIT#9cOk{mF(P}jWgP|VR3&h~_-EzZXs%ze1 z%?|qQtlzwwIkTp4(Rni&lb#D80zbQxzzZZ9J^;&CeaEs@-*tPOyZ0YKtx8o%34+&Z=&;9EDwu@PR6TklbTP$6+h6jFjOD}#S z#Bgx5p?B-Oyb>;(tPBWt|DQWA3rFCQzrnN1;Qf2ys%dcRd2r*oaL+sNr!MO2C3Riz zPoq&2=tVa5WXQw)*=K*xA71-^-L8{gaORT2gAd-v!#}^jU;EW+Gz3FEpTJ-s81=-q zX{pKl=9l+>@!jTKtoV8Z8@KLZiIi3yAyUC4{SeyCH6gP;BM1_0_CTd1n3$L9}_Y%)?&b>e^7;iF1p z=ee_grgXe^?m3e?g?m%86`#+~l!;?#vDj%rY4N0NtG3(oD~1lJ5I(sdMrXkv@9a91fPV_C4^f-^2#w{8PK zm&=QbM-Aw)0ic3Ngs58ssZ=T!oIi^N=g*>luD*#?-)?5}_I*76$~!#$*gxZNy72k@ z_<(0$UUJ&|t+v*Va6eUhNvu80EVfpXk`hVp;{a#jaD><2{*e78N-?5=AA=LeIsi(?BJ!>{!D^a%e-@^apN_Yb(GVrb_Xy0;XRryN+aV0t(HJA z*ijM#u-P3XXtiW@)6=@VEx->PD&@eT(te#Aadt`2Kzmiza>@n4=2m*Rwp%Z%28Qr|a!Ej%Yuh z!|7tp`t6J^%I_$3jL6O6j+?Kcrmm5yng&`d_Kr?qS($0vef#yNZ6`ac_kEyEMg#U% zC&6H-PtOWguHMN0l4D%9U@nU;o`*R#nU9x!!&l#K>Gd}HLYJfj>#a8|KCMJfr8{Zz zg7fE0XWs0o96esi=I#5~y7K@}zVIe5KK&57q(sze6*vFjO0N6fCG0Ob#)d6>SigA} zmDP3ppMU#D0G7PBlxB;KyKcLVDHD^K5FUT-tf?%zcwVPvXH#=v0cpnG-M!zn0Msg_ zh0KIU&%Dl+)8O5^A-nB<>PS8O;MK0TnU&o2vRIY6qizb>+uHST)7#*S_VelWI##aU z#9QxveA;%}AFKvLL6VXZF&gwVHCstDCsUZ8Lt%an*_ml*HR?XKNNR7jarAg)$7_EU zTh9X$?K`M6>Rr6(0?ci#cWUdKAx3u+r)n?bRMymYeBQ$R9Kzu+8EMM5l-1vDXNbtl!R4FTBZ|SyM(4fWPX}^Vzy{KX1MJG1p&n8E&_iWh>Y9_`Z>ad6ZSw^5&mD zV&=4o>^pdrlD7K2&F<*3EYx4~W>4d@6>C|)c^5XjgL$*3a=g5nHS4$YgYRF$6_=b( zVO|bR%~sxg=OeCcdpNvx=YD_GYBdFI^Ydn9rm=nZL0(((As5V>L5sysePatbS?L`Y z@T|-am6L)vw8bImi+k>uD|+HJYGMauUOmZ znA6QE%$_lc9eYZ6=EXm9*@C$^oNkt_T*v59`8;^9TQCGKuYyNEP#&`O$jqaQpV>TuU3CK4W)IkpE> zI2=Zo)N@#BwHmxWKPjgE53lgU>#iUtJA*I3-oTe%Z|Hb^>ZGyU*4DxWVE)`0_yYkx z{(Lo`e6a?A%ycu4-1mQzj$l9IOf9PRAIe`wo=bkf}?Yj?l zTRXV+dkb;7J?z+1!qL)7^tvQwPM^rGy@#l)Z$7O}c*pL~w8^#?&pfk?@|s4Y=HKuC zDNEj4%KnmL>@PWnTCL`i3(w{9h35k>ZcGt(-ue%`|Ivz$&r*L>kjwY4xTvGq;Wbw- z!0Yv~cH>SC9W6txRx@Q{F*jX%dB+~_{ppST?UPlk-KfmMkUt`u!u%YnY8q&^*vQIA zV?<8oiSzdNF98@ky707K#6P+DYII47Y}mSocmKAm<2diVN_y zJ8-nu+`n75D(2g+sBI+EoYG?lCA-5#i?x;Fq7gCKug~Yl>GF_fPU&_OO?#^oT~cDF zj!AtLiGbB+Cp9IxPc42zp)d}otD`hC7y`kda$dUGr^A2gA~7MM+jC;IwUU}*>TSQjr2ZLVUao)aOyM<4_Si@~U{5}`VozZFCNF;*Q?!es5{>1R*4M667 z-S<-6^R&_e=ApmBff{)C9;MR~ZFTjV{s4RXID$y3UKzTeE2WinWTmG@bwf!kYVIb3 z4wuIllYxJ321_^+!Q=7us^@94QIFRjh}js+NlA%hq^I^cJaTG^DXL(n?dCZ>Ew#^0 zIW-#fpawf_tyj7WcG{X7_H<5^i)PoKSEozrGT2MYt9kf|m-*`3&4fZ>w(i`|%GDbI zm^6O$Y3r+0D$;w{pBTP$32^)Tu9qihU|NCl_d9yr8(8Zk34`{75o1*P+nO} zVqyY?`MKz#{01e4w|oBtZ*~#z8-5A1N5PtszSq^yuMGd11a?i>^(u5FQtFyuM$Gh(}8VmVp zu=g2A?_$UppFcoZbv@%o=11Kto>QIla#p|NG~ zCf5)`42}_I`0lapI~GMNHx<_Z->!olps~e9hB+lB!A>m0JuTIQ)9ob?h@#K!0P}wb zg19~2e$~!1%qh6NUOX`vXITg_yi6RSgl}OCCc{3}=qyw$ss_XUnaeIAa#PHcp zOlv5^{qDW7#YT1?@7mj`)haU6QfX-F#TcPNh_lZ4T-f=PQe%&md1GM5lQ6EE9;pEJ zO;)nfdTvG67cmX@6q5l|syNqi*rJVA(5F3+nU)d?}U$?{N$CWW`y3=A)@+93H+qzK99;lw|SVb*GdD)#t1RP2}e}L-xX2!&z zBT_IFqO7W(yqpY-`sik85<(1LtsbSBxH@gzRm~8mhMet7-E0cKi#N~hXQrl>u zxG*mghR)xnKb6i-PH$MNoge$PnmKp8eX5DirPj-7vv6`3z!gM;H)W8 zW?}krpE3i}$)*;-)idDgbKrt;z2BzKAE3OZfl>K61JVj_K*Aj$5C~G+Xkldj(9SDa zQPW6XPI}Cz0kk<>G_}|%%FBO}=8-~(KJobjRMs|-v&0On*PS?Q^iS2qmHXADA!;mP6hP+rqOR)%@Nf}Mf9b8i=k zL@2GSBPTO0CgUaaMyu0Jlf_PP;fMjBK*85Sx6002ovPDHLk FV1fXWGPD2y diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_change_position.png index e1b2d28ffadaea2a4160933f5fd7a34d0bb8d85d..b4c1d342b62e7935a81c4087dfcaa4bb6edd74f2 100644 GIT binary patch literal 7166 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 literal 7157 zcmb_hWmr^Ew;lwMZs`y)P?QE~l(s;^kp}5>hK?D!L`q7Ukw%8@8W50f2OMAshwknn z?(yIKb${INo9CRFIp=xSI{WOs-nHKSuJCv2s+44`WFQcT@}-)R76?S(0sOZhAqKvE zj-Y?&YC2_&=eXI9PZb^6_@r-)BF7EN>hw#-i%Pt!OZqjV!LZFAe@X&x zJSMqO$%AL@Q8^xnn>@jH3y`WP2*;qi`=XxQRv~_YSECcd*(Ex@`rL*uZY^rufFk&ZaQ^Q)#O3AWjyXwZ0Owri($@xwCCjrhfYQH;bg3Nbm$Y?2uNy99q zeD-E?!ALcH_la7 z6j4WK1pyk8-(yyad4a#PFLF0?9$r2Fx_)jY{2R^NClVifj>L zGS*USOqV!%w1(rPQY|cYG}V1U^&m(trD=U0>SbeBBismQF4EnQ6>^5b$HZ&jr6WY{ z*H$7r}GuALig@C%q1?YV(u+WSrrJ{k(A+}Z$~@)@&bc?eBV-`erJIg709 z;^bMKmAmC@_m`<4ORmoYnEG^^S0IO{M7DY5xCCF z9c6kdRSA8}XUTrGOi8}1EqaAR4P*^RQ%7fE<$N$)y6jU>(_G+*ImHnKk8|)m_U0Ap zp2_unxmxJ&UchGBb5r_xzNW3VH5)#s(@r%{>t#8)P%sb)S>PWw6%z}ye_|Pv57aK& zsCJJZB;yM!2iv}mKp}QDnhEl1T-utv10ukxZ(z(7vjiE_g60F$gX>*5kCLWK_M)8aXa<;yD1R3})XgSq~* z`XRU~0&PI~%wll+-!XYP;FIeg&JD5``>`u}m2k>ieD)GrZjmQRrSn-2jTd)Er+gtF zNJcYMa#Rqp(IQ_?Y6VCjj;oh)8&r>4w?6SU(|_B#kx2F_nY@+>H>+GxwW}Le8{JQDP@+PH6N}9xM-Kc18YJXyQB>-52vj`99EK9xSR!bCQ}F$dHZF zLqCj-b%Ogw0nnwEKMP!+BHnkdN)fkp&~bG1sMkF{lX{F??-8>WvG2NG0Iba0V6Mz(eDPHYg;psEa`aH#Y+9bh5@~-pH{oo^&KN*e||6j4ZEWE)E>qN+1a)12s`yYA?ORE zHa|Xm8Oic~c8ksO{aaZi29j6W60XS;`foH{ILK|NZd6;jmaPtm?2TtE59&a%H9<73(e7_(AReX@k`705w1TR?4XvvfXc9y zPp|_>dAT;%;$}`Fq%=1!wiqcX_<5UhY8qU61^0aXfUSgXamJe>HWAb(aY&-Z&+ipV&x87 z-L^{3C@_cS*r7`aqfhzjQk0-FOm|Q#EFfpRyGO{{4B}Dgd!P>Lisozs*2CMOpwn(8 zYYx>jn(9rq?_y))@Np)egDB|5gGdgy-wUvnj^PQwv9vqaukV=Ak%>NBPL0ECZPwKUTPXb^n!lMdCduS-`(}~dx{!5R`4sjHe)J$!{|N)* zqVT5yHBhw9_}&=TwSt=w;j#lgp89>oGScx)hAZo&PjjZyen`hhc76{8nm~S^U`*A? z$)(Mc`Th69%rlFXz1E#l?d41%E8dz@Tmy6^M9`fX-b@&XI_%Lw;;awMB#S0GGkV(I z720!eXorK5+{~LzySLST?iqQE-(8PJ4HeZG=r-Z(tp<~SHH#F8R5s(4tRNRYr2KVZ zCKqK0!Tf9rzdJuKV`PT$uffjS(XCv$Eh zmU+rbcWHh1{)$xVJ3Nu`p!#6sP5=^UIi`|6VQ*h^H5X>Faj;vu%;RH`b(T1}z)}Li z3HEJB@sPVn5ZkD$*qxSSFDdQ7xjJ1nPa3+MDS$FzXOK;K*Mwy-x$?~hc&#@N4w@`| zb)@=+RX){J@E5Ap_nVlB<4WJ_L?6gRGkMO7Va1af6>vUnOn1L5wOq%3qKVK|H5o|G z2t|t+9?oMLUcobE68?-^ve!LdoBGs!cENY8mLNwdR!Jl#igCPBYAgDu? zj$vSxWpa%^-R2`5mwVmUKM^LU!^S(>Qq3!I?K2*8ZK&4zDii#;ohXr_Pb(-ez;v4Y zVA;ZYf1+&rZ$Bkg?|4$`4`izC{H-)E-_!XaY-z{XD08R9NmNUqzN?p}?}dz0eIdhNSW6Wq>S&APN? zkX#litk<&6_Zy97*4FGow>A_Q#DpW}TyKM-1|7#Z?E#AT)mB)`2s`42APh?w8mh8> zvwn_cDbUzt+&dO91l?A;`)h_xEl*46x|n4^-kz8;>S^(*86jKk=6vsp@yh6}E6Ol*$nZ+{Er1iu z({n;DD9+WUH!8}vJukSE#J`N|C*rWoo zADK6ctUOq&30Icz+Vv^CGrjX!CMTBO7g^07NVvQ4?2oavqo?^e97B= zM|$AqfprxC@Z>5HFG%kS{JgWPu#T48D3q=kV^XskOqaOL79o%>X8#zd(90vStl|!n zo%xP1&(j}CUgx8*5zE{hFtYym7L!Zo$&fja$cwS{K{af(+S-_(*Oznhgr<9GJ7~KK zr+&&0g0|DVhu$2mKIiYB{8=!kj8u_jG?2b}{vF#n1zB&lk%e_xFlI^@U>yszJpSr1 zZo#k)pSL$Jzny&tKa#cRnwdNB;m^DQnI;S2Skb=4hPyS4fTx|M`0PA0cJc^`taXV1!#My%Kw@Oqvv}1CI zjM>7X$6PN5%{E{Pc}B2AbyNgnW>iDYiMo$D-@Vf}h1%9E1l?z5SXk zdBqpbBGj^bcaNf;MzV>-{hNH3m7#m=RN7wERQ{cQm;T)dqezvUYeezsz)LmARR@9pdU6^4I2$azq2bIKz*wNYi?Wsu=E??Osh^C5f|4toR;I$J_#w52UxtCzV)jg>C^r0ImjNi=@CMGwswZew<7h9*mav zm8+}uz0Qt28tND3ck>;aG?C*JJGzLvJMv*qO?H+-I(MMQ>9*B+o1)@%nmS*xM+C-S z5sI&oiPv5b$ulpjKkypLBmvKSQe zf#55`ez`^3ck1@0Y^i9@`bFM_xvmpI-8A%O8R|&m3eRC#d;o-tMLz}xE8#G_Lfx#N z=B(YG=+5e6J#3`N*WegF&cNH+ILKyccw)9;Qx@G^Fk=?gvos+1uBW}|Bj(_U89Y@L zZ0_Tjo!Cl-3o)?Jvn@3{tc;b`?yZ{b+(t$43$8Wr<`$F%s5c{uSYZi z3n1gqr}hhMPAzObGa8(e*KJ!r>U&I)*Fp9OfYGroeF-DmO)p6bzCQV@=F0_oP?t_Z zhTxae0*vt&ex3}sH22-Exg^(`gJZTbsWlfq;%E%hYU9Lgn%BbL7{rk;!hnhQeu~dg z-xvYtMilstt9b{#-a|PCrt25~x~ng;h*UJra0%aS}O{ichi!--Bbv zj|Lahs?OQJTX@vlRMmVimJt2Jl1A_1HYzB(dGVHX_&V97WWSkA zUPR_JY|g0geYoGkFJa{h{gN)55gfDu&p!BA-I1p+R0zo8vnGvk!0tIX`uf`7Q~llL zPkClg2}91v^TTJ>oz#qt$9(R8hmfPFn#evw=EmO?HGs|alL*4Atj5*c$M4%iZy zC2A<2CP#WZn@P~(&o*`uCWmlrqehb+mHA_3@d?xQpk9-OYSCNn zSB%au#QuYibl-j{ds(GU5!-~vN&tc-?-!@1I#DWbGbJOIYXb}n6-j8*mc`k;mWz|i zgeXNilBl1*pc4?eZQU_-{S4Zsez!yRAvPGjb)SZLQK$|D2!!>rv%cd)2LpQbC}SDT z0s5!G%(})1t~!jWv3yxnBv*bgMDGS;$5ri@8@dOeNcO))pA_tQ6)!hY4TCBlBdxbG z#_VpNNluE^9F{9J(wMyytJQtfv)06<59wm;s8vm1g=G)0P#SQBqQB794cgonovwOA z{O*UDKHU3zwe(D(PEo!0;bMwNIEWDz7vZAtxv*w3;l#XD@~ZKb?*sVJ9T`7G_(3a! zRU5+hhCP>mI>D1pxOO{#p)VS@?%nsAtZ9%$A{p4|xkZ&ks{eeRYs&j;bV}vbmwmzY z1%s@YB(z*f*w;Q)A)1z*P8x%kj$53bwu$@ngX^zAIpnxGruNoMzmBwp3k$;NjEfS)@v1bO%no{)}ufSaP`MJE$aQ&buc7FY- z&4eTdwG%aN;^bkj@A)k$?al0!BJ|Rp!td{uXnX6Yev4~2JbwRYo-3GBRL;-eCsFJr zsXNFeKQ?gMp=dodbWx0$TPv|+DQjJq{-&1|>9O&>tpSwwJ-V#Q{DAC>^ulGB8lcRl zF-Bx)T6V;>_y=M)a%!_eWLy}}B!!iXK3mw;)S6dF@(Miq8r$Kc=3Vb0^=C{4uooOo zua>Hjr^#&?41JZCU2XF>Ux9>3act}vK)-nTdz1wPq`2w&7LL#pcHDFU`yX!#l?Wai ziJ9WmyII^^(kOF`RX?_{yba8MOcK)mPgsXFGc2dj%LV9(-i;+;Y>EdXjU-#{`F;k1 z9tSAUb~W?I=e#|=4<2F}j&e=s+yI3>qj*a_)xZ(Jm$0<2*(Ei(>Rlko0!prWU0Alv z!Pn#(AqFb6onyD1hpF(Y5KF?sYu9H_xwYQ&GIZgb0mN20kHFK)JX z8;V;cu?dn3a)>9W(T@V>yg-h-J`G}`2q7U>BvRyV$B_UniTk3lt7~q7?WkQ%uB}+EZH-e@A^p4#BUKnFks^ouiyHxV)a#fK^8T7- z15F4=VKvfx1hLq3YwyzW{NDNQIpIuudbFFGbcbN<>`y(^b|7wToe>aq_ aL diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_custom_send_icon.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_custom_send_icon.png index 64f0a52dd18510ed944e7a331c416b432686d648..18869a9cc0b999112477b7b7c29980ed57cb14c1 100644 GIT binary patch literal 6878 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 literal 6877 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 J%2cd^{tpJE%Qyf4 diff --git a/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png b/docs/docs_screenshots/test/message_input/goldens/macos/message_input_quoted_message.png index 9fa9e71732ac6dacd7a85cb9e365ead79d8c130a..55b646490205bf9a9b844aa40f990aef29751555 100644 GIT binary patch literal 12857 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 IM6N<$g2{MLjsO4v delta 5611 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 FV1fXWGPD2y diff --git a/docs/docs_screenshots/test/message_input/stream_message_composer_test.dart b/docs/docs_screenshots/test/message_input/stream_message_composer_test.dart index 6a7621bb7..a5d89b7bb 100644 --- a/docs/docs_screenshots/test/message_input/stream_message_composer_test.dart +++ b/docs/docs_screenshots/test/message_input/stream_message_composer_test.dart @@ -1,6 +1,7 @@ import 'package:alchemist/alchemist.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; import 'package:record/record.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_core_flutter/stream_core_flutter.dart' as core; @@ -188,6 +189,29 @@ void main() { }, ); + goldenTest( + 'slow mode active', + fileName: 'message_composer_slow_mode', + constraints: const BoxConstraints.tightFor(width: 375, height: 100), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + + setupMockChannel( + client: client, + clientState: clientState, + channel: channel, + channelState: channelState, + ); + + when(channel.getRemainingCooldown).thenReturn(10); + + return _buildMessageInputScaffold(client: client, channel: channel); + }, + ); + goldenTest( 'message input with quoted message', fileName: 'message_input_quoted_message', diff --git a/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart new file mode 100644 index 000000000..2296f7f28 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart @@ -0,0 +1,83 @@ +import 'package:alchemist/alchemist.dart'; +import 'package:connectivity_plus/connectivity_plus.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:record/record.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import '../fakes.dart'; +import '../mocks.dart'; + +void main() { + final originalRecordPlatform = RecordPlatform.instance; + setUp(() => RecordPlatform.instance = FakeRecordPlatform()); + tearDown(() => RecordPlatform.instance = originalRecordPlatform); + + goldenTest( + 'slow mode active', + fileName: 'message_composer_slow_mode', + constraints: const BoxConstraints.tightFor(width: 400, height: 120), + builder: () { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + final channelState = MockChannelState(); + final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + + when(() => client.state).thenReturn(clientState); + when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); + when(() => clientState.currentUserStream).thenAnswer((_) => Stream.value(OwnUser(id: 'user-id'))); + when(() => channel.lastMessageAt).thenReturn(lastMessageAt); + when(() => channel.lastMessageAtStream).thenAnswer((_) => Stream.value(lastMessageAt)); + when(() => channel.state).thenReturn(channelState); + when(() => channel.client).thenReturn(client); + when(channel.getRemainingCooldown).thenReturn(10); + when(() => channel.isMuted).thenReturn(false); + when(() => channel.isMutedStream).thenAnswer((_) => Stream.value(false)); + when(() => channel.isPinned).thenReturn(false); + when(() => channel.isPinnedStream).thenAnswer((_) => Stream.value(false)); + when(() => channel.isDistinct).thenReturn(false); + when(() => channel.extraDataStream).thenAnswer((_) => Stream.value({'name': 'test'})); + when(() => channel.extraData).thenReturn({'name': 'test'}); + when(() => channel.name).thenReturn('test'); + when(() => channel.nameStream).thenAnswer((_) => Stream.value('test')); + when(() => channel.image).thenReturn(null); + when(() => channel.imageStream).thenAnswer((_) => Stream.value(null)); + when(() => channelState.membersStream).thenAnswer( + (_) => Stream.value([Member(userId: 'user-id', user: User(id: 'user-id'))]), + ); + when(() => channelState.members).thenReturn([Member(userId: 'user-id', user: User(id: 'user-id'))]); + when(() => channelState.messages).thenReturn([]); + when(() => channelState.messagesStream).thenAnswer((_) => Stream.value([])); + when(() => channelState.draft).thenReturn(null); + when(() => channelState.draftStream).thenAnswer((_) => Stream.value(null)); + when(() => channelState.pinnedMessages).thenReturn([]); + when(() => channelState.pinnedMessagesStream).thenAnswer((_) => Stream.value([])); + when(() => channelState.read).thenReturn([]); + when(() => channelState.readStream).thenAnswer((_) => Stream.value([])); + when(() => channelState.currentUserReadStream).thenAnswer((_) => Stream.value(null)); + when(() => channelState.channelState).thenReturn(const ChannelState()); + when(() => channelState.channelStateStream).thenAnswer((_) => Stream.value(const ChannelState())); + + return MaterialApp( + debugShowCheckedModeBanner: false, + home: StreamChat( + client: client, + connectivityStream: Stream.value([ConnectivityResult.mobile]), + child: StreamChannel( + channel: channel, + child: Scaffold( + body: Column( + children: [ + const Expanded(child: SizedBox()), + StreamMessageComposer(), + ], + ), + ), + ), + ), + ); + }, + ); +} From 290b90d0600b8fdfaa6304e367eaab5f8a8f8f03 Mon Sep 17 00:00:00 2001 From: renefloor <15101411+renefloor@users.noreply.github.com> Date: Wed, 20 May 2026 11:31:07 +0000 Subject: [PATCH 08/11] chore: Update Goldens --- .../goldens/ci/message_composer_slow_mode.png | Bin 0 -> 2898 bytes .../goldens/ci/message_composer_slow_mode.png | Bin 0 -> 2857 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docs/docs_screenshots/test/message_input/goldens/ci/message_composer_slow_mode.png create mode 100644 packages/stream_chat_flutter/test/src/message_input/goldens/ci/message_composer_slow_mode.png diff --git a/docs/docs_screenshots/test/message_input/goldens/ci/message_composer_slow_mode.png b/docs/docs_screenshots/test/message_input/goldens/ci/message_composer_slow_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..85bf3344f3bfca6bc670312077f718168994ec6e GIT binary patch literal 2898 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 Date: Wed, 20 May 2026 13:43:25 +0200 Subject: [PATCH 09/11] cleanup test import --- .../message_composer_slow_mode_test.dart | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart b/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart index 2296f7f28..f1f005554 100644 --- a/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart +++ b/packages/stream_chat_flutter/test/src/message_input/message_composer_slow_mode_test.dart @@ -1,5 +1,4 @@ import 'package:alchemist/alchemist.dart'; -import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:mocktail/mocktail.dart'; @@ -45,9 +44,19 @@ void main() { when(() => channel.image).thenReturn(null); when(() => channel.imageStream).thenAnswer((_) => Stream.value(null)); when(() => channelState.membersStream).thenAnswer( - (_) => Stream.value([Member(userId: 'user-id', user: User(id: 'user-id'))]), + (_) => Stream.value([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]), ); - when(() => channelState.members).thenReturn([Member(userId: 'user-id', user: User(id: 'user-id'))]); + when(() => channelState.members).thenReturn([ + Member( + userId: 'user-id', + user: User(id: 'user-id'), + ), + ]); when(() => channelState.messages).thenReturn([]); when(() => channelState.messagesStream).thenAnswer((_) => Stream.value([])); when(() => channelState.draft).thenReturn(null); From 5daa01ac0f622e05a1a240e732d0f05df0ef5a86 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 20 May 2026 13:57:17 +0200 Subject: [PATCH 10/11] only add callback when onAttachmentButtonPressed is not null --- .../components/message_composer/message_composer_leading.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart index 5916f3931..2ad6f65c5 100644 --- a/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart +++ b/packages/stream_chat_flutter/lib/src/components/message_composer/message_composer_leading.dart @@ -60,7 +60,7 @@ class DefaultStreamMessageComposerLeading extends StatelessWidget { type: StreamButtonType.outline, size: StreamButtonSize.large, isFloating: props.isFloating, - onPressed: props.isSlowModeActive ? null : () => props.onAttachmentButtonPressed?.call(), + onPressed: props.isSlowModeActive ? null : props.onAttachmentButtonPressed, ), ), SizedBox(width: context.streamSpacing.xs), From 168a2ee0ae95095e819051723be92477d61cbcc4 Mon Sep 17 00:00:00 2001 From: Rene Floor Date: Wed, 20 May 2026 14:10:18 +0200 Subject: [PATCH 11/11] simplify side padding --- .../lib/src/message_input/stream_chat_message_input.dart | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart b/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart index 6c00be23e..d0015a617 100644 --- a/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input/stream_chat_message_input.dart @@ -238,7 +238,7 @@ class _StreamChatMessageInputContent extends StatelessWidget { ); return Container( - padding: EdgeInsets.only(top: spacing.md), + padding: EdgeInsets.only(top: spacing.md, right: spacing.md, left: spacing.md), decoration: widget.isFloating ? null : BoxDecoration( @@ -249,13 +249,11 @@ class _StreamChatMessageInputContent extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.end, children: [ - SizedBox(width: spacing.md), StreamMessageComposerLeading(props: componentProps), Expanded( child: StreamMessageComposerInput(props: inputProps), ), StreamMessageComposerTrailing(props: componentProps), - SizedBox(width: spacing.md), ], ), );