Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ import io.getstream.chat.android.compose.sample.feature.channel.isGroupChannel
import io.getstream.chat.android.compose.sample.ui.channel.DirectChannelInfoActivity
import io.getstream.chat.android.compose.sample.ui.channel.GroupChannelInfoActivity
import io.getstream.chat.android.compose.sample.ui.component.CustomChatComponentFactory
import io.getstream.chat.android.compose.sample.ui.component.CustomMentionStyleFactory
import io.getstream.chat.android.compose.sample.ui.location.LocationPickerTabFactory
import io.getstream.chat.android.compose.sample.vm.SharedLocationViewModelFactory
import io.getstream.chat.android.compose.state.mediagallerypreview.MediaGalleryPreviewResultType
Expand All @@ -80,6 +81,7 @@ import io.getstream.chat.android.compose.ui.messages.attachments.factory.Attachm
import io.getstream.chat.android.compose.ui.messages.composer.MessageComposer
import io.getstream.chat.android.compose.ui.messages.list.MessageList
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.ui.theme.ComposerInputFieldTheme
import io.getstream.chat.android.compose.ui.theme.MessageComposerTheme
import io.getstream.chat.android.compose.ui.theme.MessageOptionsTheme
import io.getstream.chat.android.compose.ui.theme.MessageTheme
Expand Down Expand Up @@ -145,7 +147,13 @@ class MessagesActivity : ComponentActivity() {
val colors = if (isInDarkMode) StreamColors.defaultDarkColors() else StreamColors.defaultColors()
val typography = StreamTypography.defaultTypography()
val shapes = StreamShapes.defaultShapes()
val messageComposerTheme = MessageComposerTheme.defaultTheme(isInDarkMode, typography, shapes, colors)
val messageComposerTheme = MessageComposerTheme
.defaultTheme(isInDarkMode, typography, shapes, colors)
.copy(
inputField = ComposerInputFieldTheme.defaultTheme(
mentionStyleFactory = CustomMentionStyleFactory(colors.primaryAccent),
),
)
val ownMessageTheme = MessageTheme.defaultOwnTheme(isInDarkMode, typography, shapes, colors)
val attachmentsPickerTabFactories = AttachmentsPickerTabFactories.defaultFactories() +
LocationPickerTabFactory(viewModelFactory = SharedLocationViewModelFactory(cid))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2014-2025 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.sample.ui.component

import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.SpanStyle
import io.getstream.chat.android.compose.ui.theme.MentionStyleFactory
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.Mention

/**
* A custom implementation of [MentionStyleFactory] that applies a specific color to user mentions.
*
* @param color The color to apply to user mentions.
*/
class CustomMentionStyleFactory(private val color: Color) : MentionStyleFactory {

override fun styleFor(mention: Mention): SpanStyle? = when (mention) {
is Mention.User -> SpanStyle(color = color)
else -> null
}
}
23 changes: 18 additions & 5 deletions stream-chat-android-compose/api/stream-chat-android-compose.api
Original file line number Diff line number Diff line change
Expand Up @@ -1669,7 +1669,7 @@ public final class io/getstream/chat/android/compose/ui/components/composer/Cool
}

public final class io/getstream/chat/android/compose/ui/components/composer/InputFieldKt {
public static final fun InputField (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZILandroidx/compose/foundation/BorderStroke;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/text/KeyboardOptions;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
public static final fun InputField (Ljava/lang/String;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;ZILandroidx/compose/foundation/BorderStroke;Landroidx/compose/foundation/layout/PaddingValues;Landroidx/compose/foundation/text/KeyboardOptions;Landroidx/compose/ui/text/input/VisualTransformation;Lkotlin/jvm/functions/Function3;Landroidx/compose/runtime/Composer;II)V
}

public final class io/getstream/chat/android/compose/ui/components/composer/MessageInputKt {
Expand Down Expand Up @@ -3367,24 +3367,27 @@ public final class io/getstream/chat/android/compose/ui/theme/ComposerCancelIcon
public final class io/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme {
public static final field $stable I
public static final field Companion Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme$Companion;
public synthetic fun <init> (Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JLkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JLio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JLio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Landroidx/compose/ui/graphics/Shape;
public final fun component2-0d7_KjU ()J
public final fun component3 ()Landroidx/compose/ui/text/TextStyle;
public final fun component4-0d7_KjU ()J
public final fun copy-3bbok98 (Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;J)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
public static synthetic fun copy-3bbok98$default (Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
public final fun component5 ()Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;
public final fun copy-wffgcV4 (Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JLio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
public static synthetic fun copy-wffgcV4$default (Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;Landroidx/compose/ui/graphics/Shape;JLandroidx/compose/ui/text/TextStyle;JLio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;ILjava/lang/Object;)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
public fun equals (Ljava/lang/Object;)Z
public final fun getBackgroundColor-0d7_KjU ()J
public final fun getBorderShape ()Landroidx/compose/ui/graphics/Shape;
public final fun getCursorBrushColor-0d7_KjU ()J
public final fun getMentionStyleFactory ()Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;
public final fun getTextStyle ()Landroidx/compose/ui/text/TextStyle;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class io/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme$Companion {
public final fun defaultTheme (Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamColors;Landroidx/compose/runtime/Composer;II)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
public final fun defaultTheme (Lio/getstream/chat/android/compose/ui/theme/StreamTypography;Lio/getstream/chat/android/compose/ui/theme/StreamShapes;Lio/getstream/chat/android/compose/ui/theme/StreamColors;Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;Landroidx/compose/runtime/Composer;II)Lio/getstream/chat/android/compose/ui/theme/ComposerInputFieldTheme;
}

public final class io/getstream/chat/android/compose/ui/theme/ComposerLinkPreviewTheme {
Expand Down Expand Up @@ -3458,6 +3461,15 @@ public final class io/getstream/chat/android/compose/ui/theme/IconStyle {
public fun toString ()Ljava/lang/String;
}

public abstract interface class io/getstream/chat/android/compose/ui/theme/MentionStyleFactory {
public static final field Companion Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory$Companion;
public abstract fun styleFor (Lio/getstream/chat/android/ui/common/feature/messages/composer/mention/Mention;)Landroidx/compose/ui/text/SpanStyle;
}

public final class io/getstream/chat/android/compose/ui/theme/MentionStyleFactory$Companion {
public final fun getNoStyle ()Lio/getstream/chat/android/compose/ui/theme/MentionStyleFactory;
}

public final class io/getstream/chat/android/compose/ui/theme/MessageBackgroundShapes {
public static final field $stable I
public fun <init> (Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;Landroidx/compose/ui/graphics/Shape;)V
Expand Down Expand Up @@ -4992,6 +5004,7 @@ public final class io/getstream/chat/android/compose/viewmodel/messages/MessageC
public final fun seekRecordingTo (F)V
public final fun selectCommand (Lio/getstream/chat/android/models/Command;)V
public final fun selectMention (Lio/getstream/chat/android/models/User;)V
public final fun selectMention (Lio/getstream/chat/android/ui/common/feature/messages/composer/mention/Mention;)V
public final fun sendMessage (Lio/getstream/chat/android/models/Message;Lio/getstream/result/call/Call$Callback;)V
public static synthetic fun sendMessage$default (Lio/getstream/chat/android/compose/viewmodel/messages/MessageComposerViewModel;Lio/getstream/chat/android/models/Message;Lio/getstream/result/call/Call$Callback;ILjava/lang/Object;)V
public final fun sendRecording ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,18 +37,23 @@ import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.ui.util.buildAnnotatedMessageText
import io.getstream.chat.android.compose.ui.theme.ComposerInputFieldTheme
import io.getstream.chat.android.compose.ui.theme.StreamColors
import io.getstream.chat.android.compose.ui.theme.StreamTypography
import io.getstream.chat.android.compose.ui.util.buildAnnotatedInputText

/**
* Custom input field that we use for our UI. It's fairly simple - shows a basic input with clipped
Expand All @@ -65,6 +70,8 @@ import io.getstream.chat.android.compose.ui.util.buildAnnotatedMessageText
* @param border The [BorderStroke] that will appear around the input field.
* @param innerPadding The padding inside the input field, around the label or input.
* @param keyboardOptions The [KeyboardOptions] to be applied to the input.
* @param visualTransformation The [VisualTransformation] to be applied to the input. By default, it applies text
* styling and link styling.
* @param decorationBox Composable function that represents the input field decoration as it's filled with content.
*/
@Composable
Expand All @@ -77,6 +84,11 @@ public fun InputField(
border: BorderStroke = BorderStroke(1.dp, ChatTheme.colors.borders),
innerPadding: PaddingValues = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
keyboardOptions: KeyboardOptions = KeyboardOptions(capitalization = KeyboardCapitalization.Sentences),
visualTransformation: VisualTransformation = DefaultInputFieldVisualTransformation(
inputFieldTheme = ChatTheme.messageComposerTheme.inputField,
typography = ChatTheme.typography,
colors = ChatTheme.colors,
),
decorationBox: @Composable (innerTextField: @Composable () -> Unit) -> Unit,
) {
var textState by remember { mutableStateOf(TextFieldValue(text = value)) }
Expand All @@ -94,8 +106,6 @@ public fun InputField(
}

val theme = ChatTheme.messageComposerTheme.inputField
val typography = ChatTheme.typography
val colors = ChatTheme.colors
val description = stringResource(id = R.string.stream_compose_cd_message_input)

BasicTextField(
Expand All @@ -113,19 +123,7 @@ public fun InputField(
onValueChange(it.text)
}
},
visualTransformation = {
val styledText = buildAnnotatedMessageText(
text = it.text,
textColor = theme.textStyle.color,
textFontStyle = typography.body.fontStyle,
linkStyle = TextStyle(
color = colors.primaryAccent,
textDecoration = TextDecoration.Underline,
),
mentionsColor = colors.primaryAccent,
)
TransformedText(styledText, OffsetMapping.Identity)
},
visualTransformation = visualTransformation,
textStyle = theme.textStyle,
cursorBrush = SolidColor(theme.cursorBrushColor),
decorationBox = { innerTextField -> decorationBox(innerTextField) },
Expand All @@ -136,6 +134,36 @@ public fun InputField(
)
}

/**
* Default visual transformation for the [InputField] composable.
* Applies text styling and link styling to the input text.
*
* @param inputFieldTheme The theme for the input field.
* @param typography The typography styles to be used.
* @param colors The color palette to be used.
*/
private class DefaultInputFieldVisualTransformation(
val inputFieldTheme: ComposerInputFieldTheme,
val typography: StreamTypography,
val colors: StreamColors,
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val textColor = inputFieldTheme.textStyle.color
val fontStyle = typography.body.fontStyle
val linkStyle = TextStyle(
color = colors.primaryAccent,
textDecoration = TextDecoration.Underline,
)
val transformed = buildAnnotatedInputText(
text = text.text,
textColor = textColor,
textFontStyle = fontStyle,
linkStyle = linkStyle,
)
return TransformedText(transformed, OffsetMapping.Identity)
}
}

@Preview
@Composable
private fun InputFieldPreview() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,22 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.input.OffsetMapping
import androidx.compose.ui.text.input.TransformedText
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.unit.dp
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.ui.theme.ComposerInputFieldTheme
import io.getstream.chat.android.compose.ui.theme.StreamColors
import io.getstream.chat.android.compose.ui.theme.StreamTypography
import io.getstream.chat.android.compose.ui.util.buildAnnotatedInputText
import io.getstream.chat.android.models.Attachment
import io.getstream.chat.android.ui.common.feature.messages.composer.capabilities.canSendMessage
import io.getstream.chat.android.ui.common.feature.messages.composer.mention.Mention
import io.getstream.chat.android.ui.common.state.messages.Edit
import io.getstream.chat.android.ui.common.state.messages.Reply
import io.getstream.chat.android.ui.common.state.messages.composer.MessageComposerState
Expand Down Expand Up @@ -70,6 +81,13 @@ public fun MessageInput(
val (value, attachments, activeAction) = messageComposerState
val canSendMessage = messageComposerState.canSendMessage()

val visualTransformation = MessageInputVisualTransformation(
inputFieldTheme = ChatTheme.messageComposerTheme.inputField,
typography = ChatTheme.typography,
colors = ChatTheme.colors,
mentions = messageComposerState.selectedMentions,
)

InputField(
modifier = modifier,
value = value,
Expand All @@ -78,6 +96,7 @@ public fun MessageInput(
enabled = canSendMessage,
innerPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp),
keyboardOptions = keyboardOptions,
visualTransformation = visualTransformation,
decorationBox = { innerTextField ->
Column {
if (activeAction is Reply) {
Expand Down Expand Up @@ -125,6 +144,40 @@ public fun MessageInput(
)
}

/**
* Visual transformation applied to the message input field.
* Applies text styling, link styling, and mention styling to the input text.
*
* @param inputFieldTheme The theme for the input field.
* @param typography The typography styles to be used.
* @param colors The color palette to be used.
* @param mentions The set of mentions to be styled in the input text.
*/
private class MessageInputVisualTransformation(
val inputFieldTheme: ComposerInputFieldTheme,
val typography: StreamTypography,
val colors: StreamColors,
val mentions: Set<Mention>,
) : VisualTransformation {
override fun filter(text: AnnotatedString): TransformedText {
val textColor = inputFieldTheme.textStyle.color
val fontStyle = typography.body.fontStyle
val linkStyle = TextStyle(
color = colors.primaryAccent,
textDecoration = TextDecoration.Underline,
)
val transformed = buildAnnotatedInputText(
text = text.text,
textColor = textColor,
textFontStyle = fontStyle,
linkStyle = linkStyle,
mentions = mentions,
mentionStyleFactory = inputFieldTheme.mentionStyleFactory,
)
return TransformedText(transformed, OffsetMapping.Identity)
}
}

/**
* The default number of lines allowed in the input. The message input will become scrollable after
* this threshold is exceeded.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import androidx.compose.ui.unit.sp
import io.getstream.chat.android.compose.R
import io.getstream.chat.android.compose.ui.theme.ChatTheme
import io.getstream.chat.android.compose.ui.theme.StreamTypography
import io.getstream.chat.android.compose.ui.util.buildAnnotatedMessageText
import io.getstream.chat.android.compose.ui.util.buildAnnotatedInputText

/**
* Custom input field that we use for our Poll option UI. It's fairly simple - shows a basic input with clipped
Expand Down Expand Up @@ -122,15 +122,14 @@ public fun PollOptionInput(
}
},
visualTransformation = {
val styledText = buildAnnotatedMessageText(
val styledText = buildAnnotatedInputText(
text = it.text,
textColor = textColor,
textFontStyle = typography.body.fontStyle,
linkStyle = TextStyle(
color = colors.primaryAccent,
textDecoration = TextDecoration.Underline,
),
mentionsColor = colors.primaryAccent,
)
TransformedText(styledText, OffsetMapping.Identity)
},
Expand Down
Loading
Loading