diff --git a/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt b/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt index 54d08f993c7..e296dd0b1dc 100644 --- a/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt +++ b/app/src/main/kotlin/com/wire/android/ui/common/AttachmentButton.kt @@ -32,8 +32,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Text import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -43,7 +43,6 @@ import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import com.wire.android.R import com.wire.android.ui.common.spacers.VerticalSpace @@ -90,7 +89,6 @@ fun AttachmentButton( text = text, maxLines = 2, textAlign = TextAlign.Center, - overflow = TextOverflow.Ellipsis, style = labelStyle, color = MaterialTheme.wireColorScheme.onBackground, ) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt index 252eb358246..18caf570ff2 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/ConversationScreen.kt @@ -30,9 +30,8 @@ import androidx.compose.animation.shrinkOut import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn @@ -55,6 +54,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusManager import androidx.compose.ui.platform.LocalContext @@ -832,16 +832,21 @@ fun MessageList( } } - Scaffold( - floatingActionButton = { JumpToLastMessageButton(lazyListState = lazyListState) }, + Box( + contentAlignment = Alignment.BottomEnd, + modifier = Modifier + .fillMaxSize() + .background(color = colorsScheme().backgroundVariant), content = { LazyColumn( state = lazyListState, reverseLayout = true, + // calculating bottom padding to have space for [UsersTypingIndicator] + contentPadding = PaddingValues( + bottom = dimensions().typingIndicatorHeight - (dimensions().messageItemBottomPadding / 2) + ), modifier = Modifier - .fillMaxHeight() - .fillMaxWidth() - .background(color = colorsScheme().backgroundVariant) + .fillMaxSize() ) { itemsIndexed(lazyPagingMessages, key = { _, uiMessage -> uiMessage.header.messageId @@ -891,6 +896,7 @@ fun MessageList( } } } + JumpToLastMessageButton(lazyListState = lazyListState) }) } @@ -905,7 +911,6 @@ fun JumpToLastMessageButton( exit = shrinkOut { it } ) { SmallFloatingActionButton( - modifier = Modifier.offset(y = dimensions().spacing18x), onClick = { coroutineScope.launch { lazyListState.animateScrollToItem(0) } }, containerColor = MaterialTheme.wireColorScheme.onSecondaryButtonDisabled, contentColor = MaterialTheme.wireColorScheme.secondaryButtonDisabled, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt index 549b5bb8267..cc13e9be82d 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/conversations/UsersTypingIndicator.kt @@ -78,16 +78,17 @@ fun UsersTypingIndicatorForConversation( @Composable fun UsersTypingIndicator(usersTyping: List) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .height(dimensions().spacing24x) - .background( - color = colorsScheme().surface, - shape = RoundedCornerShape(dimensions().corner14x), - ) - ) { - if (usersTyping.isNotEmpty()) { + if (usersTyping.isNotEmpty()) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(bottom = dimensions().spacing4x) + .height(dimensions().typingIndicatorHeight) + .background( + color = colorsScheme().surfaceVariant, + shape = RoundedCornerShape(dimensions().corner14x), + ) + ) { val rememberTransition = rememberInfiniteTransition(label = stringResource(R.string.animation_label_typing_indicator_horizontal_transition)) UsersTypingAvatarPreviews(usersTyping) diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt index bba329322cf..27fc8130319 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/AdditionalOptions.kt @@ -21,6 +21,7 @@ package com.wire.android.ui.home.messagecomposer import android.net.Uri +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.wrapContentSize @@ -28,6 +29,7 @@ import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.home.conversations.model.UriAsset import com.wire.android.ui.home.messagecomposer.recordaudio.RecordAudioComponent import com.wire.android.ui.home.messagecomposer.state.AdditionalOptionMenuState @@ -54,7 +56,7 @@ fun AdditionalOptionsMenu( onRichOptionButtonClicked: (RichTextMarkdown) -> Unit, modifier: Modifier = Modifier ) { - Box(modifier) { + Box(modifier.background(colorsScheme().messageComposerBackgroundColor)) { when (additionalOptionsState) { AdditionalOptionMenuState.AttachmentAndAdditionalOptionsMenu -> { AttachmentAndAdditionalOptionsMenuItems( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt index 8ca3e7da471..a84a792ddd0 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/EnabledMessageComposer.kt @@ -123,10 +123,14 @@ fun EnabledMessageComposer( Modifier.weight(1f) } Box( + contentAlignment = Alignment.BottomCenter, modifier = expandOrHideMessagesModifier .background(color = colorsScheme().backgroundVariant) ) { messageListContent() + if (!inputStateHolder.isTextExpanded) { + UsersTypingIndicatorForConversation(conversationId = conversationId) + } if (messageComposerViewState.value.mentionSearchResult.isNotEmpty()) { MembersMentionList( membersToMention = messageComposerViewState.value.mentionSearchResult, @@ -134,7 +138,8 @@ fun EnabledMessageComposer( onMentionPicked = { pickedMention -> messageCompositionHolder.addMention(pickedMention) onClearMentionSearchResult() - } + }, + modifier = Modifier.align(Alignment.BottomCenter) ) } } @@ -145,18 +150,11 @@ fun EnabledMessageComposer( Modifier.weight(1f) } Column( + horizontalAlignment = Alignment.CenterHorizontally, modifier = fillRemainingSpaceOrWrapContent .fillMaxWidth() + .background(color = colorsScheme().backgroundVariant) ) { - Column( - modifier = Modifier - .background(color = colorsScheme().backgroundVariant) - .fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - UsersTypingIndicatorForConversation(conversationId = conversationId) - } - Box(Modifier.wrapContentSize()) { SecurityClassificationBannerForConversation( conversationId = conversationId @@ -169,6 +167,7 @@ fun EnabledMessageComposer( var cursorCoordinateY by remember { mutableStateOf(0F) } ActiveMessageComposerInput( + conversationId = conversationId, messageComposition = messageComposition.value, isTextExpanded = inputStateHolder.isTextExpanded, inputType = messageCompositionInputStateHolder.inputType, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt index a8996007906..70c22843f20 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageActions.kt @@ -55,14 +55,11 @@ fun MessageSendActions( onSendButtonClicked: () -> Unit, modifier: Modifier = Modifier ) { - Box(modifier) { - Row(Modifier.padding(end = dimensions().spacing8x)) { - SendButton( - isEnabled = sendButtonEnabled, - onSendButtonClicked = onSendButtonClicked - ) - } - } + SendButton( + isEnabled = sendButtonEnabled, + onSendButtonClicked = onSendButtonClicked, + modifier = modifier.padding(end = dimensions().spacing8x) + ) } @Composable @@ -147,9 +144,11 @@ fun MessageEditActions( @Composable private fun SendButton( isEnabled: Boolean, + modifier: Modifier = Modifier, onSendButtonClicked: () -> Unit ) { WirePrimaryIconButton( + modifier = modifier, onButtonClicked = onSendButtonClicked, iconResource = R.drawable.ic_send, contentDescription = R.string.content_description_send_button, diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerInput.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerInput.kt index a065edcf073..7a58135ea31 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerInput.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/MessageComposerInput.kt @@ -33,7 +33,6 @@ import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.material3.Divider import androidx.compose.material3.Icon import androidx.compose.material3.IconButton @@ -61,6 +60,7 @@ import com.wire.android.ui.common.colorsScheme import com.wire.android.ui.common.dimensions import com.wire.android.ui.common.textfield.WireTextField import com.wire.android.ui.common.textfield.WireTextFieldColors +import com.wire.android.ui.home.conversations.UsersTypingIndicatorForConversation import com.wire.android.ui.home.conversations.messages.QuotedMessagePreview import com.wire.android.ui.home.messagecomposer.attachments.AdditionalOptionButton import com.wire.android.ui.home.messagecomposer.state.MessageComposition @@ -68,9 +68,11 @@ import com.wire.android.ui.home.messagecomposer.state.MessageCompositionType import com.wire.android.ui.home.messagecomposer.state.MessageType import com.wire.android.ui.theme.wireColorScheme import com.wire.android.ui.theme.wireTypography +import com.wire.kalium.logic.data.id.ConversationId @Composable fun ActiveMessageComposerInput( + conversationId: ConversationId, messageComposition: MessageComposition, isTextExpanded: Boolean, inputType: MessageCompositionType, @@ -91,7 +93,8 @@ fun ActiveMessageComposerInput( ) { Column( modifier = modifier - .wrapContentSize() + .wrapContentHeight() + .fillMaxWidth() .background(inputType.backgroundColor()) ) { Divider(color = MaterialTheme.wireColorScheme.outline) @@ -110,65 +113,60 @@ fun ActiveMessageComposerInput( ) } } - Row( - modifier = Modifier - .fillMaxWidth() - .wrapContentHeight(), - verticalAlignment = Alignment.Bottom - ) { - val stretchToMaxParentConstraintHeightOrWithInBoundary = if (isTextExpanded) { - Modifier.fillMaxHeight() - } else { - Modifier.heightIn(max = dimensions().messageComposerActiveInputMaxHeight) - }.weight(1f) - if (!showOptions) { - AdditionalOptionButton( - isSelected = false, - onClick = { - onPlusClick() - }, - modifier = Modifier.padding(start = dimensions().spacing8x) + val stretchToMaxParentConstraintHeightOrWithInBoundary = if (isTextExpanded) { + Modifier.fillMaxHeight() + } else { + Modifier.heightIn(max = dimensions().messageComposerActiveInputMaxHeight) + }.weight(1F) + + if (isTextExpanded) { + Column( + horizontalAlignment = Alignment.End, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + InputContent( + conversationId = conversationId, + messageComposition = messageComposition, + isTextExpanded = true, + inputType = inputType, + inputFocused = inputFocused, + onMessageTextChanged = onMessageTextChanged, + onSendButtonClicked = onSendButtonClicked, + onChangeSelfDeletionClicked = onChangeSelfDeletionClicked, + onInputFocusedChanged = onInputFocusedChanged, + onSelectedLineIndexChanged = onSelectedLineIndexChanged, + onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged, + showOptions = showOptions, + onPlusClick = onPlusClick, + modifier = stretchToMaxParentConstraintHeightOrWithInBoundary, ) } - - MessageComposerTextInput( - inputFocused = inputFocused, - colors = inputType.inputTextColor(), - messageText = messageComposition.messageTextFieldValue, - placeHolderText = inputType.labelText(), - onMessageTextChanged = onMessageTextChanged, - singleLine = false, - onFocusChanged = onInputFocusedChanged, - onSelectedLineIndexChanged = onSelectedLineIndexChanged, - onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged, - modifier = stretchToMaxParentConstraintHeightOrWithInBoundary - ) - - if (showOptions) { - Row(Modifier.wrapContentSize()) { - if (inputType is MessageCompositionType.Composing) { - when (val messageType = inputType.messageType.value) { - is MessageType.Normal -> { - MessageSendActions( - onSendButtonClicked = onSendButtonClicked, - sendButtonEnabled = inputType.isSendButtonEnabled - ) - } - - is MessageType.SelfDeleting -> { - SelfDeletingActions( - onSendButtonClicked = onSendButtonClicked, - sendButtonEnabled = inputType.isSendButtonEnabled, - selfDeletionTimer = messageType.selfDeletionTimer, - onChangeSelfDeletionClicked = onChangeSelfDeletionClicked - ) - } - - else -> {} - } - } - } + } else { + Row( + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight(), + verticalAlignment = Alignment.Bottom + ) { + InputContent( + conversationId = conversationId, + messageComposition = messageComposition, + isTextExpanded = false, + inputType = inputType, + inputFocused = inputFocused, + onMessageTextChanged = onMessageTextChanged, + onSendButtonClicked = onSendButtonClicked, + onChangeSelfDeletionClicked, + onInputFocusedChanged = onInputFocusedChanged, + onSelectedLineIndexChanged = onSelectedLineIndexChanged, + onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged, + showOptions = showOptions, + onPlusClick = onPlusClick, + modifier = stretchToMaxParentConstraintHeightOrWithInBoundary + ) } } when (inputType) { @@ -185,6 +183,77 @@ fun ActiveMessageComposerInput( } } +// flexible composable to adapt when [MessageComposerTextInput] is expanded or collapsed +@Composable +private fun InputContent( + conversationId: ConversationId, + messageComposition: MessageComposition, + isTextExpanded: Boolean, + inputType: MessageCompositionType, + inputFocused: Boolean, + onMessageTextChanged: (TextFieldValue) -> Unit, + onSendButtonClicked: () -> Unit, + onChangeSelfDeletionClicked: () -> Unit, + onInputFocusedChanged: (Boolean) -> Unit, + onSelectedLineIndexChanged: (Int) -> Unit, + onLineBottomYCoordinateChanged: (Float) -> Unit, + showOptions: Boolean, + onPlusClick: () -> Unit, + modifier: Modifier, +) { + if (!showOptions) { + AdditionalOptionButton( + isSelected = false, + onClick = { + onPlusClick() + }, + modifier = Modifier.padding(start = dimensions().spacing8x) + ) + } + + MessageComposerTextInput( + inputFocused = inputFocused, + colors = inputType.inputTextColor(), + messageText = messageComposition.messageTextFieldValue, + placeHolderText = inputType.labelText(), + onMessageTextChanged = onMessageTextChanged, + singleLine = false, + onFocusChanged = onInputFocusedChanged, + onSelectedLineIndexChanged = onSelectedLineIndexChanged, + onLineBottomYCoordinateChanged = onLineBottomYCoordinateChanged, + modifier = modifier + ) + + Box(contentAlignment = Alignment.BottomEnd, modifier = if (isTextExpanded) Modifier.fillMaxWidth() else Modifier) { + if (isTextExpanded) { + Box(modifier = Modifier.align(Alignment.BottomCenter)) { + UsersTypingIndicatorForConversation(conversationId = conversationId) + } + } + if (showOptions) { + if (inputType is MessageCompositionType.Composing) { + when (val messageType = inputType.messageType.value) { + is MessageType.Normal -> { + MessageSendActions( + onSendButtonClicked = onSendButtonClicked, + sendButtonEnabled = inputType.isSendButtonEnabled + ) + } + + is MessageType.SelfDeleting -> { + SelfDeletingActions( + onSendButtonClicked = onSendButtonClicked, + sendButtonEnabled = inputType.isSendButtonEnabled, + selfDeletionTimer = messageType.selfDeletionTimer, + onChangeSelfDeletionClicked = onChangeSelfDeletionClicked + ) + } + } + } + } + } +} + @OptIn(ExperimentalComposeUiApi::class) @Composable private fun MessageComposerTextInput( diff --git a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/UiMention.kt b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/UiMention.kt index 040d75cd558..3de75504dec 100644 --- a/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/UiMention.kt +++ b/app/src/main/kotlin/com/wire/android/ui/home/messagecomposer/UiMention.kt @@ -18,9 +18,6 @@ package com.wire.android.ui.home.messagecomposer import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.Divider import androidx.compose.material3.MaterialTheme @@ -49,34 +46,30 @@ data class UiMention( fun MembersMentionList( membersToMention: List, searchQuery: String, - onMentionPicked: (Contact) -> Unit + onMentionPicked: (Contact) -> Unit, + modifier: Modifier ) { - Column( - modifier = Modifier.fillMaxHeight(), - verticalArrangement = Arrangement.Bottom + if (membersToMention.isNotEmpty()) Divider() + LazyColumn( + modifier = modifier.background(colorsScheme().background), + reverseLayout = true ) { - if (membersToMention.isNotEmpty()) Divider() - LazyColumn( - modifier = Modifier.background(colorsScheme().background), - reverseLayout = true - ) { - membersToMention.forEach { - if (it.membership != Membership.Service) { - item { - MemberItemToMention( - avatarData = it.avatarData, - name = it.name, - label = it.label, - membership = it.membership, - clickable = Clickable { onMentionPicked(it) }, - searchQuery = searchQuery, - modifier = Modifier - ) - Divider( - color = MaterialTheme.wireColorScheme.divider, - thickness = Dp.Hairline - ) - } + membersToMention.forEach { + if (it.membership != Membership.Service) { + item { + MemberItemToMention( + avatarData = it.avatarData, + name = it.name, + label = it.label, + membership = it.membership, + clickable = Clickable { onMentionPicked(it) }, + searchQuery = searchQuery, + modifier = Modifier + ) + Divider( + color = MaterialTheme.wireColorScheme.divider, + thickness = Dp.Hairline + ) } } } diff --git a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt index 9162db37502..c8bc7b15aa8 100644 --- a/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt +++ b/app/src/main/kotlin/com/wire/android/ui/theme/WireDimensions.kt @@ -85,6 +85,7 @@ data class WireDimensions( val messageComposerPaddingEnd: Dp, val systemMessageIconSize: Dp, val systemMessageIconLargeSize: Dp, + val typingIndicatorHeight: Dp, // TextFields val textFieldMinHeight: Dp, val textFieldCornerSize: Dp, @@ -328,7 +329,8 @@ private val DefaultPhonePortraitWireDimensions: WireDimensions = WireDimensions( conversationOptionsItemMinHeight = 57.dp, ongoingCallLabelHeight = 28.dp, audioMessageHeight = 48.dp, - importedMediaAssetSize = 120.dp + importedMediaAssetSize = 120.dp, + typingIndicatorHeight = 24.dp ) private val DefaultPhoneLandscapeWireDimensions: WireDimensions = DefaultPhonePortraitWireDimensions