diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt index 13a9575241b..1a1b1907fd9 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/pages/MessageListPage.kt @@ -62,6 +62,7 @@ open class MessageListPage { companion object { val inputField get() = By.res("Stream_ComposerInputField") val sendButton get() = By.res("Stream_ComposerSendButton") + val cooldownIndicator get() = By.res("Stream_ComposerCooldownIndicator") val saveButton get() = By.res("Stream_ComposerSaveButton") val recordAudioButton get() = By.res("Stream_ComposerAudioRecordingButton") val commandsButton get() = By.res("Stream_ComposerCommandsButton") diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt index be888f50503..7f92ca48985 100644 --- a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/robots/UserRobotMessageListAsserts.kt @@ -29,6 +29,7 @@ import io.getstream.chat.android.compose.uiautomator.findObject import io.getstream.chat.android.compose.uiautomator.findObjects import io.getstream.chat.android.compose.uiautomator.height import io.getstream.chat.android.compose.uiautomator.isDisplayed +import io.getstream.chat.android.compose.uiautomator.isEnabled import io.getstream.chat.android.compose.uiautomator.retryOnStaleObjectException import io.getstream.chat.android.compose.uiautomator.seconds import io.getstream.chat.android.compose.uiautomator.wait @@ -239,6 +240,23 @@ fun UserRobot.assertComposerText(expectedText: String): UserRobot { return this } +fun UserRobot.assertCooldownIsShown(): UserRobot { + assertTrue(Composer.cooldownIndicator.waitToAppear().isDisplayed()) + assertFalse(Composer.sendButton.isDisplayed()) + return this +} + +fun UserRobot.assertCooldownIsNotShown(): UserRobot { + assertFalse(Composer.cooldownIndicator.waitToDisappear().isDisplayed()) + return this +} + +fun UserRobot.assertComposerIsDisabledInSlowMode(): UserRobot { + assertFalse(Composer.inputField.isEnabled()) + assertFalse(Composer.attachmentsButton.isEnabled()) + return this +} + fun UserRobot.assertScrollToBottomButton(isDisplayed: Boolean): UserRobot { if (isDisplayed) { assertTrue(MessageListPage.MessageList.scrollToBottomButton.waitToAppear().isDisplayed()) diff --git a/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SlowModeTests.kt b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SlowModeTests.kt new file mode 100644 index 00000000000..bd76a8283e9 --- /dev/null +++ b/stream-chat-android-compose-sample/src/androidTestE2eDebug/kotlin/io/getstream/chat/android/compose/tests/SlowModeTests.kt @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2014-2026 Stream.io Inc. All rights reserved. + * + * Licensed under the Stream License; + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://github.com/GetStream/stream-chat-android/blob/main/LICENSE + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.getstream.chat.android.compose.tests + +import io.getstream.chat.android.compose.robots.assertComposerIsDisabledInSlowMode +import io.getstream.chat.android.compose.robots.assertCooldownIsShown +import io.getstream.chat.android.compose.sample.ui.InitTestActivity +import io.qameta.allure.kotlin.Allure.step +import org.junit.Test + +class SlowModeTests : StreamTestCase() { + + override fun initTestActivity() = InitTestActivity.UserLogin + + private val cooldownDuration = 15 + private val message = "message" + + @Test + fun test_cooldownIsShownWhenNewMessageIsSent() { + step("GIVEN slow mode is enabled on the channel") { + backendRobot.setCooldown(enabled = true, duration = cooldownDuration) + } + step("AND user opens the channel") { + userRobot.login().openChannel() + } + step("WHEN user sends a new message") { + userRobot.sendMessage(message) + } + step("THEN slow mode is active and the cooldown is shown") { + userRobot.assertCooldownIsShown() + } + } + + @Test + fun test_composerIsDisabledWhenSlowModeIsActive() { + step("GIVEN slow mode is enabled on the channel") { + backendRobot.setCooldown(enabled = true, duration = cooldownDuration) + } + step("AND user opens the channel") { + userRobot.login().openChannel() + } + step("WHEN user sends a new message") { + userRobot.sendMessage(message) + } + step("THEN the cooldown is shown and the composer is locked") { + userRobot + .assertCooldownIsShown() + .assertComposerIsDisabledInSlowMode() + } + } +} diff --git a/stream-chat-android-compose/api/stream-chat-android-compose.api b/stream-chat-android-compose/api/stream-chat-android-compose.api index 3e8235e73af..86dd403d9e4 100644 --- a/stream-chat-android-compose/api/stream-chat-android-compose.api +++ b/stream-chat-android-compose/api/stream-chat-android-compose.api @@ -2010,6 +2010,7 @@ public final class io/getstream/chat/android/compose/ui/messages/composer/Compos public final fun getLambda$-1206314464$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$-1267828661$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$-1344661276$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda$-1483541199$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$-173232017$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$1180759242$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; public final fun getLambda$1309976052$stream_chat_android_compose_release ()Lkotlin/jvm/functions/Function2; diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/CoolDownIndicator.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/CoolDownIndicator.kt index 93f98511a33..134a518d85d 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/CoolDownIndicator.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/CoolDownIndicator.kt @@ -26,6 +26,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.getstream.chat.android.compose.ui.theme.ChatTheme @@ -43,6 +44,7 @@ public fun CoolDownIndicator( ) { Box( modifier = modifier + .testTag("Stream_ComposerCooldownIndicator") .size(48.dp) .padding(8.dp) .background(shape = RoundedCornerShape(24.dp), color = ChatTheme.colors.textDisabled), diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt index 7db7bf4303a..b95e007005b 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/components/composer/MessageInput.kt @@ -98,11 +98,18 @@ public fun MessageInput( recordingActions: AudioRecordingActions = AudioRecordingActions.None, onActiveCommandDismiss: () -> Unit = {}, ) { + // While slow mode is active the whole composer is locked, so the border uses the disabled colour. + val isInCoolDown = messageComposerState.coolDownTime > 0 + val borderColor = if (isInCoolDown) { + ChatTheme.colors.borderUtilityDisabled + } else { + ChatTheme.colors.borderCoreDefault + } Column( modifier = modifier .border( width = 1.dp, - color = ChatTheme.colors.borderCoreDefault, + color = borderColor, shape = MessageInputShape, ) .then( @@ -315,7 +322,6 @@ private fun MessageComposerInputSlowModePreview() { internal fun MessageComposerInputSlowMode() { MessageInput( messageComposerState = PreviewMessageComposerState.copy( - inputValue = "Slow mode, wait 9s", coolDownTime = 9, ), ) diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt index 55b84203cee..126a65d9f9e 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/MessageComposer.kt @@ -544,6 +544,24 @@ internal fun MessageComposerFixedStyleWithCommandSuggestions() { ) } +@Preview +@Composable +private fun MessageComposerSlowModePreview() { + ChatTheme { + MessageComposerSlowMode() + } +} + +@Composable +internal fun MessageComposerSlowMode() { + MessageComposer( + messageComposerState = PreviewMessageComposerState.copy( + coolDownTime = 9, + ), + onSendMessage = { _, _ -> }, + ) +} + @Preview(showBackground = true) @Composable private fun MessageComposerFloatingStylePreview() { diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerInputCenterContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerInputCenterContent.kt index ff219ddb0a3..14624effcbe 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerInputCenterContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerInputCenterContent.kt @@ -113,6 +113,7 @@ internal fun MessageComposerInputCenterContent( if (value.isEmpty()) { TextFieldPlaceholder( canSendMessage = canSendMessage, + coolDownTime = state.coolDownTime, activeCommandDescriptionRes = state.activeCommand?.placeholderRes, ) } @@ -120,7 +121,7 @@ internal fun MessageComposerInputCenterContent( }, maxLines = TextFieldMaxLines, singleLine = false, - enabled = canSendMessage, + enabled = canSendMessage && state.coolDownTime <= 0, keyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences), ) } @@ -128,12 +129,20 @@ internal fun MessageComposerInputCenterContent( @Composable private fun TextFieldPlaceholder( canSendMessage: Boolean, + coolDownTime: Int, @StringRes activeCommandDescriptionRes: Int?, ) { - val text = if (canSendMessage) { - stringResource(activeCommandDescriptionRes ?: UiCommonR.string.stream_ui_message_composer_placeholder_default) - } else { - stringResource(UiCommonR.string.stream_ui_message_composer_placeholder_cannot_send_messages) + val text = when { + coolDownTime > 0 -> + stringResource(UiCommonR.string.stream_ui_message_composer_placeholder_slow_mode, coolDownTime) + + canSendMessage -> + stringResource( + activeCommandDescriptionRes ?: UiCommonR.string.stream_ui_message_composer_placeholder_default, + ) + + else -> + stringResource(UiCommonR.string.stream_ui_message_composer_placeholder_cannot_send_messages) } Text( text = text, diff --git a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerLeadingContent.kt b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerLeadingContent.kt index 016157cd9a6..521506e1102 100644 --- a/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerLeadingContent.kt +++ b/stream-chat-android-compose/src/main/java/io/getstream/chat/android/compose/ui/messages/composer/internal/MessageComposerLeadingContent.kt @@ -60,6 +60,9 @@ internal fun MessageComposerLeadingContent( val isCommandActive = messageInputState.activeCommand != null + // During slow mode the button stays visible but becomes non-interactive. + val isInCoolDown = messageInputState.coolDownTime > 0 + val showAttachmentButton = canSendMessage && !isRecording && canUploadFile && !isCommandActive AnimatedContent( @@ -73,6 +76,7 @@ internal fun MessageComposerLeadingContent( AttachmentPickerButton( isPickerVisible = isAttachmentPickerVisible, iconRotation = iconRotation, + enabled = !isInCoolDown, onClick = onAttachmentsClick, onClickLabel = onAttachmentsClickLabel, ) @@ -84,6 +88,7 @@ internal fun MessageComposerLeadingContent( private fun AttachmentPickerButton( isPickerVisible: Boolean, iconRotation: Float, + enabled: Boolean = true, onClick: () -> Unit, onClickLabel: String? = null, ) { @@ -106,7 +111,7 @@ private fun AttachmentPickerButton( .padding(end = 8.dp) .border( width = 1.dp, - color = ChatTheme.colors.borderCoreDefault, + color = if (enabled) ChatTheme.colors.borderCoreDefault else ChatTheme.colors.borderUtilityDisabled, shape = CircleShape, ) .then( @@ -120,11 +125,14 @@ private fun AttachmentPickerButton( .testTag("Stream_ComposerAttachmentsButton") .semantics { stateDescription = pickerStateDescription - onClick(label = resolvedActionLabel) { - onClick() - true + if (enabled) { + onClick(label = resolvedActionLabel) { + onClick() + true + } } }, + enabled = enabled, colors = IconButtonDefaults.filledIconButtonColors( containerColor = ChatTheme.colors.backgroundCoreElevation1, disabledContainerColor = ChatTheme.colors.backgroundCoreElevation1, diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerTest.kt index 0acd3cef699..e09d016af35 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/MessageComposerTest.kt @@ -28,6 +28,7 @@ import io.getstream.chat.android.compose.ui.messages.composer.MessageComposerFlo import io.getstream.chat.android.compose.ui.messages.composer.MessageComposerFloatingStyleWithCommandSuggestions import io.getstream.chat.android.compose.ui.messages.composer.MessageComposerFloatingStyleWithUserSuggestions import io.getstream.chat.android.compose.ui.messages.composer.MessageComposerFloatingStyleWithVisibleAttachmentPicker +import io.getstream.chat.android.compose.ui.messages.composer.MessageComposerSlowMode import org.junit.Rule import org.junit.Test @@ -64,6 +65,13 @@ internal class MessageComposerTest : PaparazziComposeTest { } } + @Test + fun `slow mode`() { + snapshotWithDarkMode(contentAlignment = Alignment.BottomCenter) { + MessageComposerSlowMode() + } + } + @Test fun `floating style`() { snapshotWithDarkMode(contentAlignment = Alignment.BottomCenter) { diff --git a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/MessageComposerScreenTest.kt b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/MessageComposerScreenTest.kt index 0426563b6ba..aebebb6499e 100644 --- a/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/MessageComposerScreenTest.kt +++ b/stream-chat-android-compose/src/test/kotlin/io/getstream/chat/android/compose/ui/messages/composer/MessageComposerScreenTest.kt @@ -17,13 +17,17 @@ package io.getstream.chat.android.compose.ui.messages.composer import androidx.annotation.UiThread +import androidx.compose.ui.test.assertIsEnabled +import androidx.compose.ui.test.assertIsNotEnabled import androidx.compose.ui.test.hasText import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onNodeWithText import androidx.test.ext.junit.runners.AndroidJUnit4 import io.getstream.chat.android.client.test.MockedChatClientTest import io.getstream.chat.android.compose.ui.theme.ChatTheme import io.getstream.chat.android.compose.viewmodel.messages.MessageComposerViewModel +import io.getstream.chat.android.models.ChannelCapabilities import io.getstream.chat.android.previewdata.PreviewUserData import io.getstream.chat.android.randomCommand import io.getstream.chat.android.randomFloat @@ -102,6 +106,42 @@ internal class MessageComposerScreenTest : MockedChatClientTest { composeTestRule.onNodeWithText("Also send in Channel").assertExists() } + @Test + @UiThread + fun `slow mode disables input and attachment button`() { + val capabilities = setOf(ChannelCapabilities.SEND_MESSAGE, ChannelCapabilities.UPLOAD_FILE) + whenever(mockViewModel.messageComposerState) doReturn + MutableStateFlow(MessageComposerState(ownCapabilities = capabilities, coolDownTime = 9)) + + composeTestRule.setContent { + ChatTheme { + MessageComposer(viewModel = mockViewModel) + } + } + + composeTestRule.onNodeWithText("9").assertExists() + composeTestRule.onNodeWithText("Slow mode, wait 9s…").assertExists() + composeTestRule.onNodeWithTag("Stream_ComposerInputField").assertIsNotEnabled() + composeTestRule.onNodeWithTag("Stream_ComposerAttachmentsButton").assertIsNotEnabled() + } + + @Test + @UiThread + fun `composer stays interactive without cooldown`() { + val capabilities = setOf(ChannelCapabilities.SEND_MESSAGE, ChannelCapabilities.UPLOAD_FILE) + whenever(mockViewModel.messageComposerState) doReturn + MutableStateFlow(MessageComposerState(ownCapabilities = capabilities, coolDownTime = 0)) + + composeTestRule.setContent { + ChatTheme { + MessageComposer(viewModel = mockViewModel) + } + } + + composeTestRule.onNodeWithTag("Stream_ComposerInputField").assertIsEnabled() + composeTestRule.onNodeWithTag("Stream_ComposerAttachmentsButton").assertIsEnabled() + } + @Test @UiThread fun `audio recording`() { diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_slow_mode.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_slow_mode.png index 6fb67eab144..a1ef9d8df93 100644 Binary files a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_slow_mode.png and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerInputTest_slow_mode.png differ diff --git a/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerTest_slow_mode.png b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerTest_slow_mode.png new file mode 100644 index 00000000000..4e653ec9c7b Binary files /dev/null and b/stream-chat-android-compose/src/test/snapshots/images/io.getstream.chat.android.compose.ui.messages_MessageComposerTest_slow_mode.png differ diff --git a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt index 154d038c31f..d1d052a3b03 100644 --- a/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt +++ b/stream-chat-android-e2e-test/src/main/kotlin/io/getstream/chat/android/e2e/test/robots/BackendRobot.kt @@ -56,6 +56,12 @@ public class BackendRobot( return this } + public fun setCooldown(enabled: Boolean, duration: Int): BackendRobot { + waitForMockServerToStart() + mockServer.postRequest("config/cooldown?enabled=$enabled&duration=$duration") + return this + } + public fun revokeToken(duration: Int = 5) { waitForMockServerToStart() mockServer.postRequest("jwt/revoke_token?duration=$duration") diff --git a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt index ef95a92f87a..86954c367b4 100644 --- a/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt +++ b/stream-chat-android-ui-common/src/main/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerController.kt @@ -206,22 +206,6 @@ public class MessageComposerController( initialValue = false, ) - /** - * Signals if the user needs to wait before sending the next message. - * - * Depending on roles & permissions setup in the dashboard, some user groups are allowed - * to send messages instantly even if the slow mode is enabled for the channel. - * - * [SharingStarted.Eagerly] because this [StateFlow] has no collectors, its value is only - * ever read directly. - */ - private val isSlowModeActive = ownCapabilities.map { it.contains(ChannelCapabilities.SLOW_MODE) } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - /** * Signals whether slow-mode should be ignored (even if it's active). * @@ -1335,7 +1319,10 @@ public class MessageComposerController( * @param lastSentMessageDate The date of the last message. */ private fun handleLastSentMessageDate(cooldownInterval: Int, lastSentMessageDate: Date?) { - val isSlowModeActive = cooldownInterval > 0 && isSlowModeActive.value && !isSlowModeDisabled.value + // Not gated on the SLOW_MODE capability on purpose: the backend grants it only when + // cooldown > 0 and the user cannot skip slow mode, so these two checks stay equivalent + // and keep working if the backend later changes how that capability is granted. + val isSlowModeActive = cooldownInterval > 0 && !isSlowModeDisabled.value if (isSlowModeActive && lastSentMessageDate != null && !isInEditMode) { // Time passed since the last message was successfully sent to the server diff --git a/stream-chat-android-ui-common/src/main/res/values-es/strings.xml b/stream-chat-android-ui-common/src/main/res/values-es/strings.xml index 375e5188f4c..76009e1f657 100644 --- a/stream-chat-android-ui-common/src/main/res/values-es/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-es/strings.xml @@ -132,6 +132,7 @@ "\@username" "\@username" "Enviar un mensaje" + "Modo lento, espera %1$d s…" "Error. El fichero no se puede mostrar" "Error al descargar el archivo: %s" "Descargando… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-fr/strings.xml b/stream-chat-android-ui-common/src/main/res/values-fr/strings.xml index 880a46ecba4..ad724b60b5e 100644 --- a/stream-chat-android-ui-common/src/main/res/values-fr/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-fr/strings.xml @@ -132,6 +132,7 @@ "\@username" "\@username" "Envoyer un message" + "Mode lent, patientez %1$d s…" "Erreur. Le fichier ne peut pas être affiché" "Échec du téléchargement du fichier : %s" "Téléchargement… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-hi/strings.xml b/stream-chat-android-ui-common/src/main/res/values-hi/strings.xml index bb9c875c9fd..436cf96530e 100644 --- a/stream-chat-android-ui-common/src/main/res/values-hi/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-hi/strings.xml @@ -132,6 +132,7 @@ \@username \@username "संदेश भेजें" + "धीमा मोड, %1$d सेकंड प्रतीक्षा करें…" "त्रुटि। फ़ाइल प्रदर्शित नहीं की जा सकती" "फ़ाइल डाउनलोड करने में विफल: %s" "डाउनलोड हो रहा है… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-in/strings.xml b/stream-chat-android-ui-common/src/main/res/values-in/strings.xml index ab9385658ca..70a32fec08a 100644 --- a/stream-chat-android-ui-common/src/main/res/values-in/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-in/strings.xml @@ -131,6 +131,7 @@ "\@username" "\@username" "Kirim pesan" + "Mode lambat, tunggu %1$d dtk…" "Terjadi kesalahan. Berkas tidak dapat ditampilkan" "Gagal mengunduh file: %s" "Mengunduh… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-it/strings.xml b/stream-chat-android-ui-common/src/main/res/values-it/strings.xml index 06464835d11..40f211305fa 100644 --- a/stream-chat-android-ui-common/src/main/res/values-it/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-it/strings.xml @@ -132,6 +132,7 @@ \@username \@username "Invia un messaggio" + "Modalità lenta, attendi %1$d s…" "Errore. Impossibile visualizzare il file" "Impossibile scaricare il file: %s" "Download in corso… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-ja/strings.xml b/stream-chat-android-ui-common/src/main/res/values-ja/strings.xml index 97192e1157c..f19166cc686 100644 --- a/stream-chat-android-ui-common/src/main/res/values-ja/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-ja/strings.xml @@ -131,6 +131,7 @@ \@username \@username "メッセージを送信" + "スローモード、あと%1$d秒…" "エラー。ファイルを表示できません" "ファイルのダウンロードに失敗しました:%s" "ダウンロード中… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values-ko/strings.xml b/stream-chat-android-ui-common/src/main/res/values-ko/strings.xml index 02d871d470e..0ef38b339c7 100644 --- a/stream-chat-android-ui-common/src/main/res/values-ko/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values-ko/strings.xml @@ -131,6 +131,7 @@ \@username \@username "메시지 보내기" + "슬로우 모드, %1$d초 기다리세요…" "오류. 파일을 표시할 수 없습니다" "파일 다운로드에 실패했습니다: %s" "다운로드 중… %1$s / %2$s" diff --git a/stream-chat-android-ui-common/src/main/res/values/strings.xml b/stream-chat-android-ui-common/src/main/res/values/strings.xml index b4d68c96848..994a2d8e507 100644 --- a/stream-chat-android-ui-common/src/main/res/values/strings.xml +++ b/stream-chat-android-ui-common/src/main/res/values/strings.xml @@ -212,6 +212,7 @@ Send a message You can\'t send messages in this channel + Slow mode, wait %1$ds… Search GIFs \@username \@username diff --git a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt index 9d4f7c79bf1..eb5789e81c1 100644 --- a/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt +++ b/stream-chat-android-ui-common/src/test/kotlin/io/getstream/chat/android/ui/common/feature/messages/composer/MessageComposerControllerTest.kt @@ -25,6 +25,7 @@ import io.getstream.chat.android.client.channel.state.ChannelState import io.getstream.chat.android.client.setup.state.ClientState import io.getstream.chat.android.models.App import io.getstream.chat.android.models.AppSettings +import io.getstream.chat.android.models.ChannelCapabilities import io.getstream.chat.android.models.ChannelData import io.getstream.chat.android.models.Command import io.getstream.chat.android.models.Config @@ -158,6 +159,55 @@ internal class MessageComposerControllerTest { assertTrue(controller.state.value.pollsEnabled) } + @Test + fun `slow mode cooldown activates without the slow-mode capability`() = runTest { + // given a channel with a cooldown but without the slow-mode own-capability + val channelData = MutableStateFlow( + ChannelData(id = CHANNEL_ID, type = CHANNEL_TYPE, cooldown = 10, ownCapabilities = emptySet()), + ) + // when the current user has just sent a message + val controller = Fixture() + .givenAppSettings(mock()) + .givenAudioPlayer(mock()) + .givenClientState(User("uid1")) + .givenGlobalState() + .givenInheritedScope(testCoroutines.scope) + .givenChannelState( + channelDataState = channelData, + lastSentMessageDateState = MutableStateFlow(Date()), + ) + .get() + // then slow mode is active even though the slow-mode capability is absent + assertTrue(controller.state.value.coolDownTime > 0) + } + + @Test + fun `slow mode cooldown stays off when the user can skip slow mode`() = runTest { + // given a channel with a cooldown and the skip-slow-mode capability + val channelData = MutableStateFlow( + ChannelData( + id = CHANNEL_ID, + type = CHANNEL_TYPE, + cooldown = 10, + ownCapabilities = setOf(ChannelCapabilities.SKIP_SLOW_MODE), + ), + ) + // when the current user has just sent a message + val controller = Fixture() + .givenAppSettings(mock()) + .givenAudioPlayer(mock()) + .givenClientState(User("uid1")) + .givenGlobalState() + .givenInheritedScope(testCoroutines.scope) + .givenChannelState( + channelDataState = channelData, + lastSentMessageDateState = MutableStateFlow(Date()), + ) + .get() + // then slow mode is not active + assertEquals(0, controller.state.value.coolDownTime) + } + @Test fun `Given user mention When selectMention called Then message input is autocompleted with user name`() = runTest { // Given