Simplify message composer layout (#4884)
Co-authored-by: ElementBot <android@element.io>
This commit is contained in:
parent
6a088396ed
commit
a10734de02
17 changed files with 574 additions and 435 deletions
|
|
@ -1,175 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
||||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalMaterial3Api::class)
|
||||
|
||||
package io.element.android.features.messages.impl
|
||||
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.ime
|
||||
import androidx.compose.foundation.layout.windowInsetsPadding
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.SheetValue
|
||||
import androidx.compose.material3.rememberBottomSheetScaffoldState
|
||||
import androidx.compose.material3.rememberStandardBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.layout.Layout
|
||||
import androidx.compose.ui.layout.Measurable
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.min
|
||||
import io.element.android.libraries.core.data.tryOrNull
|
||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetScaffold
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* A [BottomSheetScaffold] that allows the sheet to be expanded the screen height
|
||||
* of the sheet contents.
|
||||
*
|
||||
* @param content The main content.
|
||||
* @param sheetContent The sheet content.
|
||||
* @param sheetDragHandle The drag handle for the sheet.
|
||||
* @param sheetSwipeEnabled Whether the sheet can be swiped. This value is ignored and swipe is disabled if the sheet content overflows.
|
||||
* @param sheetShape The shape of the sheet.
|
||||
* @param sheetTonalElevation The tonal elevation of the sheet.
|
||||
* @param sheetShadowElevation The shadow elevation of the sheet.
|
||||
* @param modifier The modifier for the layout.
|
||||
* @param sheetContentKey The key for the sheet content. If the key changes, the sheet will be remeasured.
|
||||
*/
|
||||
@Suppress(
|
||||
"ContentTrailingLambda",
|
||||
// False positive
|
||||
"MultipleEmitters",
|
||||
)
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
internal fun ExpandableBottomSheetScaffold(
|
||||
content: @Composable (padding: PaddingValues) -> Unit,
|
||||
// False positive, it's not being reused
|
||||
@Suppress("ContentSlotReused")
|
||||
sheetContent: @Composable (subcomposing: Boolean) -> Unit,
|
||||
sheetDragHandle: @Composable () -> Unit,
|
||||
sheetSwipeEnabled: Boolean,
|
||||
sheetShape: Shape,
|
||||
sheetTonalElevation: Dp,
|
||||
sheetShadowElevation: Dp,
|
||||
modifier: Modifier = Modifier,
|
||||
sheetContentKey: Int? = null,
|
||||
) {
|
||||
val scaffoldState = rememberBottomSheetScaffoldState(
|
||||
bottomSheetState = rememberStandardBottomSheetState(
|
||||
initialValue = SheetValue.PartiallyExpanded,
|
||||
skipHiddenState = true,
|
||||
)
|
||||
)
|
||||
|
||||
// If the content overflows, we disable swipe to prevent the sheet from intercepting
|
||||
// scroll events of the sheet content.
|
||||
var contentOverflows by remember { mutableStateOf(false) }
|
||||
val sheetSwipeEnabledIfPossible by remember(contentOverflows, sheetSwipeEnabled) {
|
||||
derivedStateOf {
|
||||
sheetSwipeEnabled && !contentOverflows
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(sheetSwipeEnabledIfPossible) {
|
||||
if (!sheetSwipeEnabledIfPossible) {
|
||||
scaffoldState.bottomSheetState.partialExpand()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Scaffold(
|
||||
sheetContent: @Composable () -> Unit,
|
||||
dragHandle: @Composable () -> Unit,
|
||||
peekHeight: Dp,
|
||||
) {
|
||||
BottomSheetScaffold(
|
||||
modifier = Modifier,
|
||||
scaffoldState = scaffoldState,
|
||||
sheetPeekHeight = peekHeight,
|
||||
sheetSwipeEnabled = sheetSwipeEnabledIfPossible,
|
||||
sheetDragHandle = dragHandle,
|
||||
sheetShape = sheetShape,
|
||||
content = content,
|
||||
sheetContent = { sheetContent() },
|
||||
sheetTonalElevation = sheetTonalElevation,
|
||||
sheetShadowElevation = sheetShadowElevation,
|
||||
)
|
||||
}
|
||||
|
||||
SubcomposeLayout(
|
||||
modifier = modifier.windowInsetsPadding(WindowInsets.ime),
|
||||
measurePolicy = { constraints: Constraints ->
|
||||
val sheetContentSub = subcompose(Slot.SheetContent(sheetContentKey)) { sheetContent(true) }.map {
|
||||
it.measure(Constraints(maxWidth = constraints.maxWidth))
|
||||
}.first()
|
||||
val dragHandleSub = subcompose(Slot.DragHandle) { sheetDragHandle() }.map {
|
||||
it.measure(Constraints(maxWidth = constraints.maxWidth))
|
||||
}.firstOrNull()
|
||||
val dragHandleHeight = dragHandleSub?.height?.toDp() ?: 0.dp
|
||||
|
||||
val maxHeight = constraints.maxHeight.toDp()
|
||||
val contentHeight = sheetContentSub.measuredHeight.toDp() + dragHandleHeight
|
||||
|
||||
contentOverflows = contentHeight > maxHeight
|
||||
|
||||
val peekHeight = min(
|
||||
// prevent the sheet from expanding beyond the screen
|
||||
maxHeight,
|
||||
contentHeight
|
||||
)
|
||||
|
||||
val scaffoldPlaceables = subcompose(Slot.Scaffold) {
|
||||
Scaffold({
|
||||
Layout(
|
||||
modifier = Modifier.fillMaxHeight(),
|
||||
measurePolicy = { measurables, constraints ->
|
||||
val constraintHeight = constraints.maxHeight
|
||||
val offset = tryOrNull { scaffoldState.bottomSheetState.requireOffset() } ?: 0f
|
||||
val height = Integer.max(peekHeight.roundToPx(), constraintHeight - offset.roundToInt())
|
||||
val top = measurables[0].measure(
|
||||
constraints.copy(
|
||||
minHeight = height,
|
||||
maxHeight = height
|
||||
)
|
||||
)
|
||||
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||
top.place(x = 0, y = 0)
|
||||
}
|
||||
},
|
||||
content = { sheetContent(false) }
|
||||
)
|
||||
}, sheetDragHandle, peekHeight)
|
||||
}.map { measurable: Measurable ->
|
||||
measurable.measure(constraints)
|
||||
}
|
||||
val scaffoldPlaceable = scaffoldPlaceables.first()
|
||||
layout(constraints.maxWidth, constraints.maxHeight) {
|
||||
scaffoldPlaceable.place(0, 0)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private sealed interface Slot {
|
||||
data class SheetContent(val key: Int?) : Slot
|
||||
data object DragHandle : Slot
|
||||
data object Scaffold : Slot
|
||||
}
|
||||
|
|
@ -26,22 +26,17 @@ import androidx.compose.foundation.layout.imePadding
|
|||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.draw.shadow
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
|
||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontStyle
|
||||
|
|
@ -83,11 +78,13 @@ import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorVi
|
|||
import io.element.android.features.roomcall.api.RoomCallState
|
||||
import io.element.android.libraries.androidutils.ui.hideKeyboard
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
|
||||
import io.element.android.libraries.designsystem.components.ExpandableBottomSheetLayout
|
||||
import io.element.android.libraries.designsystem.components.avatar.Avatar
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarType
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.components.rememberExpandableBottomSheetLayoutState
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
|
|
@ -112,7 +109,6 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
import io.element.android.wysiwyg.link.Link
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import timber.log.Timber
|
||||
import kotlin.random.Random
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Composable
|
||||
|
|
@ -186,69 +182,114 @@ fun MessagesView(
|
|||
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event))
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
modifier = modifier,
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
Column {
|
||||
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
|
||||
MessagesViewTopBar(
|
||||
roomName = state.roomName,
|
||||
roomAvatar = state.roomAvatar,
|
||||
isTombstoned = state.isTombstoned,
|
||||
heroes = state.heroes,
|
||||
roomCallState = state.roomCallState,
|
||||
dmUserIdentityState = state.dmUserVerificationState,
|
||||
onBackClick = { hidingKeyboard { onBackClick() } },
|
||||
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
)
|
||||
}
|
||||
val expandableState = rememberExpandableBottomSheetLayoutState()
|
||||
ExpandableBottomSheetLayout(
|
||||
modifier = modifier.fillMaxSize().imePadding().systemBarsPadding(),
|
||||
content = {
|
||||
Scaffold(
|
||||
contentWindowInsets = WindowInsets.statusBars,
|
||||
topBar = {
|
||||
Column {
|
||||
ConnectivityIndicatorView(isOnline = state.hasNetworkConnection)
|
||||
MessagesViewTopBar(
|
||||
roomName = state.roomName,
|
||||
roomAvatar = state.roomAvatar,
|
||||
isTombstoned = state.isTombstoned,
|
||||
heroes = state.heroes,
|
||||
roomCallState = state.roomCallState,
|
||||
dmUserIdentityState = state.dmUserVerificationState,
|
||||
onBackClick = { hidingKeyboard { onBackClick() } },
|
||||
onRoomDetailsClick = { hidingKeyboard { onRoomDetailsClick() } },
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
)
|
||||
}
|
||||
},
|
||||
content = { padding ->
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding)
|
||||
) {
|
||||
MessagesViewContent(
|
||||
state = state,
|
||||
onContentClick = ::onContentClick,
|
||||
onMessageLongClick = ::onMessageLongClick,
|
||||
onUserDataClick = {
|
||||
hidingKeyboard {
|
||||
state.eventSink(MessagesEvents.OnUserClicked(it))
|
||||
}
|
||||
},
|
||||
onLinkClick = { link, customTab ->
|
||||
if (customTab) {
|
||||
onLinkClick(link.url, true)
|
||||
// Do not check those links, they are internal link only
|
||||
} else {
|
||||
state.linkState.eventSink(LinkEvents.OnLinkClick(link))
|
||||
}
|
||||
},
|
||||
onReactionClick = ::onEmojiReactionClick,
|
||||
onReactionLongClick = ::onEmojiReactionLongClick,
|
||||
onMoreReactionsClick = ::onMoreReactionsClick,
|
||||
onReadReceiptClick = { event ->
|
||||
state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event))
|
||||
},
|
||||
onSendLocationClick = onSendLocationClick,
|
||||
onCreatePollClick = onCreatePollClick,
|
||||
onSwipeToReply = { targetEvent ->
|
||||
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent))
|
||||
},
|
||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
||||
knockRequestsBannerView = knockRequestsBannerView,
|
||||
)
|
||||
|
||||
SuggestionsPickerView(
|
||||
modifier = Modifier
|
||||
.shadow(10.dp)
|
||||
.background(ElementTheme.colors.bgCanvasDefault)
|
||||
.align(Alignment.BottomStart)
|
||||
.heightIn(max = 230.dp),
|
||||
roomId = state.roomId,
|
||||
roomName = state.roomName,
|
||||
roomAvatarData = state.roomAvatar,
|
||||
suggestions = state.composerState.suggestions,
|
||||
onSelectSuggestion = {
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
|
||||
}
|
||||
)
|
||||
}
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
snackbarHostState,
|
||||
modifier = Modifier.navigationBarsPadding()
|
||||
)
|
||||
},
|
||||
)
|
||||
},
|
||||
content = { padding ->
|
||||
MessagesViewContent(
|
||||
bottomSheetContent = {
|
||||
MessagesViewComposerBottomSheetContents(
|
||||
state = state,
|
||||
modifier = Modifier
|
||||
.padding(padding)
|
||||
.consumeWindowInsets(padding),
|
||||
onContentClick = ::onContentClick,
|
||||
onMessageLongClick = ::onMessageLongClick,
|
||||
onUserDataClick = {
|
||||
hidingKeyboard {
|
||||
state.eventSink(MessagesEvents.OnUserClicked(it))
|
||||
}
|
||||
onLinkClick = { url, customTab -> onLinkClick(url, customTab) },
|
||||
onRoomSuccessorClick = { roomId ->
|
||||
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
|
||||
},
|
||||
onLinkClick = { link, customTab ->
|
||||
if (customTab) {
|
||||
onLinkClick(link.url, true)
|
||||
// Do not check those links, they are internal link only
|
||||
} else {
|
||||
state.linkState.eventSink(LinkEvents.OnLinkClick(link))
|
||||
}
|
||||
},
|
||||
onReactionClick = ::onEmojiReactionClick,
|
||||
onReactionLongClick = ::onEmojiReactionLongClick,
|
||||
onMoreReactionsClick = ::onMoreReactionsClick,
|
||||
onReadReceiptClick = { event ->
|
||||
state.readReceiptBottomSheetState.eventSink(ReadReceiptBottomSheetEvents.EventSelected(event))
|
||||
},
|
||||
onSendLocationClick = onSendLocationClick,
|
||||
onCreatePollClick = onCreatePollClick,
|
||||
onSwipeToReply = { targetEvent ->
|
||||
state.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, targetEvent))
|
||||
},
|
||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
onViewAllPinnedMessagesClick = onViewAllPinnedMessagesClick,
|
||||
knockRequestsBannerView = knockRequestsBannerView,
|
||||
)
|
||||
},
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
snackbarHostState,
|
||||
modifier = Modifier.navigationBarsPadding()
|
||||
)
|
||||
sheetDragHandle = if (state.composerState.showTextFormatting) {
|
||||
@Composable { BottomSheetDragHandle() }
|
||||
} else {
|
||||
@Composable {}
|
||||
},
|
||||
isSwipeGestureEnabled = state.composerState.showTextFormatting,
|
||||
state = expandableState,
|
||||
sheetShape = if (state.composerState.showTextFormatting || state.composerState.suggestions.isNotEmpty()) {
|
||||
MaterialTheme.shapes.large
|
||||
} else {
|
||||
RectangleShape
|
||||
},
|
||||
maxBottomSheetContentHeight = 360.dp,
|
||||
)
|
||||
|
||||
ActionListView(
|
||||
|
|
@ -348,88 +389,49 @@ private fun MessagesViewContent(
|
|||
)
|
||||
}
|
||||
|
||||
// This key is used to force the sheet to be remeasured when the content changes.
|
||||
// Any state change that should trigger a height size should be added to the list of remembered values here.
|
||||
val sheetResizeContentKey = remember { mutableIntStateOf(0) }
|
||||
LaunchedEffect(
|
||||
state.composerState.textEditorState.lineCount,
|
||||
state.composerState.showTextFormatting,
|
||||
) {
|
||||
sheetResizeContentKey.intValue = Random.nextInt()
|
||||
}
|
||||
|
||||
ExpandableBottomSheetScaffold(
|
||||
sheetDragHandle = if (state.composerState.showTextFormatting) {
|
||||
@Composable { BottomSheetDragHandle() }
|
||||
} else {
|
||||
@Composable {}
|
||||
},
|
||||
sheetSwipeEnabled = state.composerState.showTextFormatting,
|
||||
sheetShape = if (state.composerState.showTextFormatting || state.composerState.suggestions.isNotEmpty()) {
|
||||
MaterialTheme.shapes.large
|
||||
} else {
|
||||
RectangleShape
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Box(modifier = Modifier.padding(paddingValues)) {
|
||||
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberScrollBehavior(
|
||||
pinnedMessagesCount = state.pinnedMessagesBannerState.pinnedMessagesCount(),
|
||||
Box {
|
||||
val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberScrollBehavior(
|
||||
pinnedMessagesCount = state.pinnedMessagesBannerState.pinnedMessagesCount(),
|
||||
)
|
||||
TimelineView(
|
||||
state = state.timelineState,
|
||||
timelineProtectionState = state.timelineProtectionState,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = { link -> onLinkClick(link, false) },
|
||||
onContentClick = onContentClick,
|
||||
onMessageLongClick = onMessageLongClick,
|
||||
onSwipeToReply = onSwipeToReply,
|
||||
onReactionClick = onReactionClick,
|
||||
onReactionLongClick = onReactionLongClick,
|
||||
onMoreReactionsClick = onMoreReactionsClick,
|
||||
onReadReceiptClick = onReadReceiptClick,
|
||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
nestedScrollConnection = scrollBehavior.nestedScrollConnection,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = state.pinnedMessagesBannerState is PinnedMessagesBannerState.Visible && scrollBehavior.isVisible,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
fun focusOnPinnedEvent(eventId: EventId) {
|
||||
state.timelineState.eventSink(
|
||||
TimelineEvents.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)
|
||||
)
|
||||
TimelineView(
|
||||
state = state.timelineState,
|
||||
timelineProtectionState = state.timelineProtectionState,
|
||||
onUserDataClick = onUserDataClick,
|
||||
onLinkClick = { link -> onLinkClick(link, false) },
|
||||
onContentClick = onContentClick,
|
||||
onMessageLongClick = onMessageLongClick,
|
||||
onSwipeToReply = onSwipeToReply,
|
||||
onReactionClick = onReactionClick,
|
||||
onReactionLongClick = onReactionLongClick,
|
||||
onMoreReactionsClick = onMoreReactionsClick,
|
||||
onReadReceiptClick = onReadReceiptClick,
|
||||
forceJumpToBottomVisibility = forceJumpToBottomVisibility,
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
nestedScrollConnection = scrollBehavior.nestedScrollConnection,
|
||||
)
|
||||
AnimatedVisibility(
|
||||
visible = state.pinnedMessagesBannerState is PinnedMessagesBannerState.Visible && scrollBehavior.isVisible,
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically(),
|
||||
) {
|
||||
fun focusOnPinnedEvent(eventId: EventId) {
|
||||
state.timelineState.eventSink(
|
||||
TimelineEvents.FocusOnEvent(eventId = eventId, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds)
|
||||
)
|
||||
}
|
||||
PinnedMessagesBannerView(
|
||||
state = state.pinnedMessagesBannerState,
|
||||
onClick = ::focusOnPinnedEvent,
|
||||
onViewAllClick = onViewAllPinnedMessagesClick,
|
||||
)
|
||||
}
|
||||
knockRequestsBannerView()
|
||||
}
|
||||
},
|
||||
sheetContent = { subcomposing: Boolean ->
|
||||
MessagesViewComposerBottomSheetContents(
|
||||
subcomposing = subcomposing,
|
||||
state = state,
|
||||
onRoomSuccessorClick = { roomId ->
|
||||
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
|
||||
},
|
||||
onLinkClick = { url, customTab -> onLinkClick(Link(url), customTab) },
|
||||
PinnedMessagesBannerView(
|
||||
state = state.pinnedMessagesBannerState,
|
||||
onClick = ::focusOnPinnedEvent,
|
||||
onViewAllClick = onViewAllPinnedMessagesClick,
|
||||
)
|
||||
},
|
||||
sheetContentKey = sheetResizeContentKey.intValue,
|
||||
sheetTonalElevation = 0.dp,
|
||||
sheetShadowElevation = if (state.composerState.suggestions.isNotEmpty()) 16.dp else 0.dp,
|
||||
)
|
||||
}
|
||||
knockRequestsBannerView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MessagesViewComposerBottomSheetContents(
|
||||
subcomposing: Boolean,
|
||||
state: MessagesState,
|
||||
onRoomSuccessorClick: (RoomId) -> Unit,
|
||||
onLinkClick: (String, Boolean) -> Unit,
|
||||
|
|
@ -440,23 +442,6 @@ private fun MessagesViewComposerBottomSheetContents(
|
|||
}
|
||||
state.userEventPermissions.canSendMessage -> {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SuggestionsPickerView(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 230.dp)
|
||||
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
|
||||
.nestedScroll(object : NestedScrollConnection {
|
||||
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||
return available
|
||||
}
|
||||
}),
|
||||
roomId = state.roomId,
|
||||
roomName = state.roomName,
|
||||
roomAvatarData = state.roomAvatar,
|
||||
suggestions = state.composerState.suggestions,
|
||||
onSelectSuggestion = {
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
|
||||
}
|
||||
)
|
||||
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
||||
if (state.composerState.suggestions.isEmpty() &&
|
||||
state.composerState.textEditorState is TextEditorState.Markdown) {
|
||||
|
|
@ -474,7 +459,6 @@ private fun MessagesViewComposerBottomSheetContents(
|
|||
MessageComposerView(
|
||||
state = state.composerState,
|
||||
voiceMessageState = state.voiceMessageComposerState,
|
||||
subcomposing = subcomposing,
|
||||
enableVoiceMessages = state.enableVoiceMessages,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import kotlinx.coroutines.launch
|
|||
internal fun MessageComposerView(
|
||||
state: MessageComposerState,
|
||||
voiceMessageState: VoiceMessageComposerState,
|
||||
subcomposing: Boolean,
|
||||
enableVoiceMessages: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -98,7 +97,6 @@ internal fun MessageComposerView(
|
|||
modifier = modifier,
|
||||
state = state.textEditorState,
|
||||
voiceMessageState = voiceMessageState.voiceMessageState,
|
||||
subcomposing = subcomposing,
|
||||
onRequestFocus = ::onRequestFocus,
|
||||
onSendMessage = ::sendMessage,
|
||||
composerMode = state.mode,
|
||||
|
|
@ -131,14 +129,12 @@ internal fun MessageComposerViewPreview(
|
|||
state = state,
|
||||
voiceMessageState = aVoiceMessageComposerState(),
|
||||
enableVoiceMessages = true,
|
||||
subcomposing = false,
|
||||
)
|
||||
MessageComposerView(
|
||||
modifier = Modifier.height(200.dp),
|
||||
state = state,
|
||||
voiceMessageState = aVoiceMessageComposerState(),
|
||||
enableVoiceMessages = true,
|
||||
subcomposing = false,
|
||||
)
|
||||
DisabledComposerView()
|
||||
}
|
||||
|
|
@ -155,7 +151,6 @@ internal fun MessageComposerViewVoicePreview(
|
|||
state = aMessageComposerState(),
|
||||
voiceMessageState = state,
|
||||
enableVoiceMessages = true,
|
||||
subcomposing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue