Timeline: clean up and fix some ui glitches
This commit is contained in:
parent
1e618cc2a9
commit
06bd7fa0c6
3 changed files with 81 additions and 42 deletions
|
|
@ -1,4 +1,8 @@
|
||||||
@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
|
@file:OptIn(
|
||||||
|
ExperimentalMaterial3Api::class,
|
||||||
|
ExperimentalMaterialApi::class,
|
||||||
|
ExperimentalComposeUiApi::class
|
||||||
|
)
|
||||||
|
|
||||||
package io.element.android.x.features.messages
|
package io.element.android.x.features.messages
|
||||||
|
|
||||||
|
|
@ -21,8 +25,10 @@ import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Alignment.Companion.End
|
import androidx.compose.ui.Alignment.Companion.End
|
||||||
import androidx.compose.ui.Alignment.Companion.Start
|
import androidx.compose.ui.Alignment.Companion.Start
|
||||||
|
import androidx.compose.ui.ExperimentalComposeUiApi
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.LastBaseline
|
import androidx.compose.ui.layout.LastBaseline
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
|
@ -48,8 +54,6 @@ import kotlinx.coroutines.launch
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.lang.Math.random
|
import java.lang.Math.random
|
||||||
|
|
||||||
private val COMPOSER_HEIGHT = 112.dp
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MessagesScreen(
|
fun MessagesScreen(
|
||||||
roomId: String,
|
roomId: String,
|
||||||
|
|
@ -64,10 +68,12 @@ fun MessagesScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
LogCompositions(tag = "MessagesScreen", msg = "Root")
|
||||||
val actionsSheetState = rememberModalBottomSheetState(
|
val itemActionsBottomSheetState = rememberModalBottomSheetState(
|
||||||
initialValue = ModalBottomSheetValue.Hidden,
|
initialValue = ModalBottomSheetValue.Hidden,
|
||||||
)
|
)
|
||||||
|
val snackbarHostState = remember { SnackbarHostState() }
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
val focusManager = LocalFocusManager.current
|
||||||
val roomTitle by viewModel.collectAsState(MessagesViewState::roomName)
|
val roomTitle by viewModel.collectAsState(MessagesViewState::roomName)
|
||||||
val roomAvatar by viewModel.collectAsState(MessagesViewState::roomAvatar)
|
val roomAvatar by viewModel.collectAsState(MessagesViewState::roomAvatar)
|
||||||
val timelineItems by viewModel.collectAsState(MessagesViewState::timelineItems)
|
val timelineItems by viewModel.collectAsState(MessagesViewState::timelineItems)
|
||||||
|
|
@ -78,7 +84,7 @@ fun MessagesScreen(
|
||||||
val composerFullScreen by composerViewModel.collectAsState(MessageComposerViewState::isFullScreen)
|
val composerFullScreen by composerViewModel.collectAsState(MessageComposerViewState::isFullScreen)
|
||||||
val composerCanSendMessage by composerViewModel.collectAsState(MessageComposerViewState::isSendButtonVisible)
|
val composerCanSendMessage by composerViewModel.collectAsState(MessageComposerViewState::isSendButtonVisible)
|
||||||
val composerText by composerViewModel.collectAsState(MessageComposerViewState::text)
|
val composerText by composerViewModel.collectAsState(MessageComposerViewState::text)
|
||||||
val snackbarHostState = remember { SnackbarHostState() }
|
|
||||||
MessagesScreenContent(
|
MessagesScreenContent(
|
||||||
roomTitle = roomTitle,
|
roomTitle = roomTitle,
|
||||||
roomAvatar = roomAvatar,
|
roomAvatar = roomAvatar,
|
||||||
|
|
@ -99,33 +105,18 @@ fun MessagesScreen(
|
||||||
Timber.v("onClick on timeline item: ${it.id}")
|
Timber.v("onClick on timeline item: ${it.id}")
|
||||||
},
|
},
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
|
focusManager.clearFocus(force = true)
|
||||||
viewModel.computeActionsSheetState(it)
|
viewModel.computeActionsSheetState(it)
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
actionsSheetState.show()
|
itemActionsBottomSheetState.show()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
snackbarHostState = snackbarHostState,
|
snackbarHostState = snackbarHostState,
|
||||||
)
|
)
|
||||||
val itemActionsSheetState by viewModel.collectAsState(prop1 = MessagesViewState::itemActionsSheetState)
|
|
||||||
TimelineItemActionsScreen(
|
TimelineItemActionsScreen(
|
||||||
sheetState = actionsSheetState,
|
viewModel = viewModel,
|
||||||
actionsSheetState = itemActionsSheetState(),
|
composerViewModel = composerViewModel,
|
||||||
onActionClicked = {
|
modalBottomSheetState = itemActionsBottomSheetState,
|
||||||
viewModel.handleItemAction(it)
|
|
||||||
coroutineScope.launch {
|
|
||||||
val targetEvent = viewModel.getTargetEvent()
|
|
||||||
when (it) {
|
|
||||||
is MessagesItemAction.Edit -> {
|
|
||||||
// Entering Edit mode, update the text in the composer.
|
|
||||||
val newComposerText =
|
|
||||||
(targetEvent?.content as? MessagesTimelineItemTextBasedContent)?.body.orEmpty()
|
|
||||||
composerViewModel.updateText(newComposerText)
|
|
||||||
}
|
|
||||||
else -> Unit
|
|
||||||
}
|
|
||||||
actionsSheetState.hide()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
snackBarContent?.let {
|
snackBarContent?.let {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
|
|
@ -185,7 +176,12 @@ fun MessagesScreenContent(
|
||||||
composerText = composerText
|
composerText = composerText
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
snackbarHost = { SnackbarHost(snackbarHostState) },
|
snackbarHost = {
|
||||||
|
SnackbarHost(
|
||||||
|
snackbarHostState,
|
||||||
|
modifier = Modifier.navigationBarsPadding()
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package io.element.android.x.features.messages
|
||||||
|
|
||||||
import com.airbnb.mvrx.MavericksViewModel
|
import com.airbnb.mvrx.MavericksViewModel
|
||||||
import com.airbnb.mvrx.MavericksViewModelFactory
|
import com.airbnb.mvrx.MavericksViewModelFactory
|
||||||
|
import com.airbnb.mvrx.Uninitialized
|
||||||
import com.airbnb.mvrx.ViewModelContext
|
import com.airbnb.mvrx.ViewModelContext
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarData
|
import io.element.android.x.designsystem.components.avatar.AvatarData
|
||||||
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
import io.element.android.x.designsystem.components.avatar.AvatarSize
|
||||||
|
|
@ -92,12 +93,8 @@ class MessagesViewModel(
|
||||||
return currentState.itemActionsSheetState.invoke()?.targetItem
|
return currentState.itemActionsSheetState.invoke()?.targetItem
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleItemAction(action: MessagesItemAction) {
|
fun handleItemAction(action: MessagesItemAction, targetEvent: MessagesTimelineItemState.MessageEvent) {
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
val currentState = awaitState()
|
|
||||||
Timber.v("Handle $action for ${currentState.itemActionsSheetState}")
|
|
||||||
val targetEvent = getTargetEvent()
|
|
||||||
?: return@launch
|
|
||||||
when (action) {
|
when (action) {
|
||||||
MessagesItemAction.Copy -> notImplementedYet()
|
MessagesItemAction.Copy -> notImplementedYet()
|
||||||
MessagesItemAction.Forward -> notImplementedYet()
|
MessagesItemAction.Forward -> notImplementedYet()
|
||||||
|
|
@ -152,7 +149,11 @@ class MessagesViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun computeActionsSheetState(messagesTimelineItemState: MessagesTimelineItemState.MessageEvent) {
|
fun computeActionsSheetState(messagesTimelineItemState: MessagesTimelineItemState.MessageEvent?) {
|
||||||
|
if (messagesTimelineItemState == null) {
|
||||||
|
setState { copy(itemActionsSheetState = Uninitialized) }
|
||||||
|
return
|
||||||
|
}
|
||||||
suspend {
|
suspend {
|
||||||
val actions =
|
val actions =
|
||||||
if (messagesTimelineItemState.content is MessagesTimelineItemRedactedContent) {
|
if (messagesTimelineItemState.content is MessagesTimelineItemRedactedContent) {
|
||||||
|
|
|
||||||
|
|
@ -7,29 +7,71 @@ import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.material.*
|
import androidx.compose.material.*
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalFocusManager
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.airbnb.mvrx.compose.collectAsState
|
||||||
import io.element.android.x.designsystem.components.VectorIcon
|
import io.element.android.x.designsystem.components.VectorIcon
|
||||||
|
import io.element.android.x.features.messages.MessagesViewModel
|
||||||
import io.element.android.x.features.messages.model.MessagesItemAction
|
import io.element.android.x.features.messages.model.MessagesItemAction
|
||||||
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
import io.element.android.x.features.messages.model.MessagesItemActionsSheetState
|
||||||
|
import io.element.android.x.features.messages.model.MessagesTimelineItemState
|
||||||
|
import io.element.android.x.features.messages.model.MessagesViewState
|
||||||
|
import io.element.android.x.features.messages.model.content.MessagesTimelineItemTextBasedContent
|
||||||
|
import io.element.android.x.features.messages.textcomposer.MessageComposerViewModel
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TimelineItemActionsScreen(
|
fun TimelineItemActionsScreen(
|
||||||
sheetState: ModalBottomSheetState,
|
viewModel: MessagesViewModel,
|
||||||
actionsSheetState: MessagesItemActionsSheetState?,
|
composerViewModel: MessageComposerViewModel,
|
||||||
onActionClicked: (MessagesItemAction) -> Unit,
|
modalBottomSheetState: ModalBottomSheetState,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
LaunchedEffect(modalBottomSheetState) {
|
||||||
|
snapshotFlow { modalBottomSheetState.currentValue }
|
||||||
|
.filter { it == ModalBottomSheetValue.Hidden }
|
||||||
|
.collect {
|
||||||
|
viewModel.computeActionsSheetState(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val itemActionsSheetState by viewModel.collectAsState(MessagesViewState::itemActionsSheetState)
|
||||||
|
|
||||||
|
fun onItemActionClicked(
|
||||||
|
itemAction: MessagesItemAction,
|
||||||
|
targetItem: MessagesTimelineItemState.MessageEvent
|
||||||
|
) {
|
||||||
|
viewModel.handleItemAction(itemAction, targetItem)
|
||||||
|
coroutineScope.launch {
|
||||||
|
val targetEvent = viewModel.getTargetEvent()
|
||||||
|
when (itemAction) {
|
||||||
|
is MessagesItemAction.Edit -> {
|
||||||
|
// Entering Edit mode, update the text in the composer.
|
||||||
|
val newComposerText =
|
||||||
|
(targetEvent?.content as? MessagesTimelineItemTextBasedContent)?.body.orEmpty()
|
||||||
|
composerViewModel.updateText(newComposerText)
|
||||||
|
}
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
modalBottomSheetState.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ModalBottomSheetLayout(
|
ModalBottomSheetLayout(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
sheetState = sheetState,
|
sheetState = modalBottomSheetState,
|
||||||
sheetContent = {
|
sheetContent = {
|
||||||
SheetContent(
|
SheetContent(
|
||||||
actionsSheetState = actionsSheetState,
|
actionsSheetState = itemActionsSheetState(),
|
||||||
onActionClicked = onActionClicked,
|
onActionClicked = ::onItemActionClicked,
|
||||||
modifier = Modifier.navigationBarsPadding()
|
modifier = Modifier.navigationBarsPadding().imePadding()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
|
|
@ -39,7 +81,7 @@ fun TimelineItemActionsScreen(
|
||||||
@Composable
|
@Composable
|
||||||
private fun SheetContent(
|
private fun SheetContent(
|
||||||
actionsSheetState: MessagesItemActionsSheetState?,
|
actionsSheetState: MessagesItemActionsSheetState?,
|
||||||
onActionClicked: (MessagesItemAction) -> Unit,
|
onActionClicked: (MessagesItemAction, MessagesTimelineItemState.MessageEvent) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
if (actionsSheetState == null || actionsSheetState.actions.isEmpty()) {
|
if (actionsSheetState == null || actionsSheetState.actions.isEmpty()) {
|
||||||
|
|
@ -54,7 +96,7 @@ private fun SheetContent(
|
||||||
items(actionsSheetState.actions) {
|
items(actionsSheetState.actions) {
|
||||||
ListItem(
|
ListItem(
|
||||||
modifier = Modifier.clickable {
|
modifier = Modifier.clickable {
|
||||||
onActionClicked(it)
|
onActionClicked(it, actionsSheetState.targetItem)
|
||||||
},
|
},
|
||||||
text = {
|
text = {
|
||||||
Text(
|
Text(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue