Timeline: clean up and fix some ui glitches

This commit is contained in:
ganfra 2022-12-02 18:14:49 +01:00
parent 1e618cc2a9
commit 06bd7fa0c6
3 changed files with 81 additions and 42 deletions

View file

@ -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()
)
},
) )
} }

View file

@ -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) {

View file

@ -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(