Merge branch 'develop' into feature/fga/avoid_deadlocks
This commit is contained in:
commit
cfd43af45c
307 changed files with 2806 additions and 1172 deletions
|
|
@ -30,7 +30,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.canBeCopied
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ fun ForwardMessagesView(
|
|||
return
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun onRoomRemoved(roomSummaryDetails: RoomSummaryDetails) {
|
||||
// TODO toggle selection when multi-selection is enabled
|
||||
state.eventSink(ForwardMessagesEvents.RemoveSelectedRoom)
|
||||
|
|
|
|||
|
|
@ -57,7 +57,6 @@ import io.element.android.features.messages.impl.media.local.LocalMediaView
|
|||
import io.element.android.features.messages.impl.media.local.MediaInfo
|
||||
import io.element.android.features.messages.impl.media.local.rememberLocalMediaViewState
|
||||
import io.element.android.libraries.architecture.Async
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.components.button.BackButton
|
||||
import io.element.android.libraries.designsystem.components.dialogs.RetryDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ internal fun AttachmentsBottomSheet(
|
|||
onDismissRequest = { isVisible = false }
|
||||
) {
|
||||
AttachmentSourcePickerMenu(
|
||||
eventSink = state.eventSink,
|
||||
state = state,
|
||||
onSendLocationClicked = onSendLocationClicked,
|
||||
)
|
||||
}
|
||||
|
|
@ -93,7 +93,7 @@ internal fun AttachmentsBottomSheet(
|
|||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
internal fun AttachmentSourcePickerMenu(
|
||||
eventSink: (MessageComposerEvents) -> Unit,
|
||||
state: MessageComposerState,
|
||||
onSendLocationClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
|
@ -102,33 +102,35 @@ internal fun AttachmentSourcePickerMenu(
|
|||
// .navigationBarsPadding() - FIXME after https://issuetracker.google.com/issues/275849044
|
||||
) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) },
|
||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) },
|
||||
icon = { Icon(Icons.Default.Collections, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_gallery)) },
|
||||
)
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) },
|
||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) },
|
||||
icon = { Icon(Icons.Default.AttachFile, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_files)) },
|
||||
)
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) },
|
||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera) },
|
||||
icon = { Icon(Icons.Default.PhotoCamera, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_photo)) },
|
||||
)
|
||||
ListItem(
|
||||
modifier = Modifier.clickable { eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) },
|
||||
modifier = Modifier.clickable { state.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera) },
|
||||
icon = { Icon(Icons.Default.Videocam, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_camera_video)) },
|
||||
)
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
eventSink(MessageComposerEvents.PickAttachmentSource.Location)
|
||||
onSendLocationClicked()
|
||||
},
|
||||
icon = { Icon(Icons.Default.LocationOn, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_location)) },
|
||||
)
|
||||
if (state.canShareLocation) {
|
||||
ListItem(
|
||||
modifier = Modifier.clickable {
|
||||
state.eventSink(MessageComposerEvents.PickAttachmentSource.Location)
|
||||
onSendLocationClicked()
|
||||
},
|
||||
icon = { Icon(Icons.Default.LocationOn, null) },
|
||||
text = { Text(stringResource(R.string.screen_room_attachment_source_location)) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -136,7 +138,9 @@ internal fun AttachmentSourcePickerMenu(
|
|||
@Composable
|
||||
internal fun AttachmentSourcePickerMenuPreview() = ElementPreview {
|
||||
AttachmentSourcePickerMenu(
|
||||
eventSink = {},
|
||||
state = aMessageComposerState(
|
||||
canShareLocation = true,
|
||||
),
|
||||
onSendLocationClicked = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,11 @@ class MessageComposerPresenter @Inject constructor(
|
|||
mutableStateOf<AttachmentsState>(AttachmentsState.None)
|
||||
}
|
||||
|
||||
val canShareLocation = remember { mutableStateOf(false) }
|
||||
LaunchedEffect(Unit) {
|
||||
canShareLocation.value = featureFlagService.isFeatureEnabled(FeatureFlags.LocationSharing)
|
||||
}
|
||||
|
||||
val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker { uri, mimeType ->
|
||||
handlePickedMedia(attachmentsState, uri, mimeType)
|
||||
}
|
||||
|
|
@ -140,23 +145,23 @@ class MessageComposerPresenter @Inject constructor(
|
|||
)
|
||||
)
|
||||
}
|
||||
MessageComposerEvents.AddAttachment -> localCoroutineScope.launchIfMediaPickerEnabled {
|
||||
MessageComposerEvents.AddAttachment -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = true
|
||||
}
|
||||
MessageComposerEvents.DismissAttachmentMenu -> showAttachmentSourcePicker = false
|
||||
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launchIfMediaPickerEnabled {
|
||||
MessageComposerEvents.PickAttachmentSource.FromGallery -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = false
|
||||
galleryMediaPicker.launch()
|
||||
}
|
||||
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launchIfMediaPickerEnabled {
|
||||
MessageComposerEvents.PickAttachmentSource.FromFiles -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = false
|
||||
filesPicker.launch()
|
||||
}
|
||||
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled {
|
||||
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = false
|
||||
cameraPhotoPicker.launch()
|
||||
}
|
||||
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launchIfMediaPickerEnabled {
|
||||
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = false
|
||||
cameraVideoPicker.launch()
|
||||
}
|
||||
|
|
@ -173,17 +178,12 @@ class MessageComposerPresenter @Inject constructor(
|
|||
hasFocus = hasFocus.value,
|
||||
mode = messageComposerContext.composerMode,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
canShareLocation = canShareLocation.value,
|
||||
attachmentsState = attachmentsState.value,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.launchIfMediaPickerEnabled(action: suspend () -> Unit) = launch {
|
||||
if (featureFlagService.isFeatureEnabled(FeatureFlags.ShowMediaUploadingFlow)) {
|
||||
action()
|
||||
}
|
||||
}
|
||||
|
||||
private fun CoroutineScope.sendMessage(
|
||||
text: String,
|
||||
updateComposerMode: (newComposerMode: MessageComposerMode) -> Unit,
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ data class MessageComposerState(
|
|||
val hasFocus: Boolean,
|
||||
val mode: MessageComposerMode,
|
||||
val showAttachmentSourcePicker: Boolean,
|
||||
val canShareLocation: Boolean,
|
||||
val attachmentsState: AttachmentsState,
|
||||
val eventSink: (MessageComposerEvents) -> Unit
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -26,12 +26,21 @@ open class MessageComposerStateProvider : PreviewParameterProvider<MessageCompos
|
|||
)
|
||||
}
|
||||
|
||||
fun aMessageComposerState() = MessageComposerState(
|
||||
text = "",
|
||||
isFullScreen = false,
|
||||
hasFocus = false,
|
||||
mode = MessageComposerMode.Normal(content = ""),
|
||||
showAttachmentSourcePicker = false,
|
||||
attachmentsState = AttachmentsState.None,
|
||||
eventSink = {}
|
||||
fun aMessageComposerState(
|
||||
text: String = "",
|
||||
isFullScreen: Boolean = false,
|
||||
hasFocus: Boolean = false,
|
||||
mode: MessageComposerMode = MessageComposerMode.Normal(content = ""),
|
||||
showAttachmentSourcePicker: Boolean = false,
|
||||
canShareLocation: Boolean = true,
|
||||
attachmentsState: AttachmentsState = AttachmentsState.None,
|
||||
) = MessageComposerState(
|
||||
text = text,
|
||||
isFullScreen = isFullScreen,
|
||||
hasFocus = hasFocus,
|
||||
mode = mode,
|
||||
showAttachmentSourcePicker = showAttachmentSourcePicker,
|
||||
canShareLocation = canShareLocation,
|
||||
attachmentsState = attachmentsState,
|
||||
eventSink = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -42,8 +42,8 @@ import kotlinx.coroutines.launch
|
|||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val backPaginationEventLimit = 20
|
||||
private const val backPaginationPageSize = 50
|
||||
private const val BACK_PAGINATION_EVENT_LIMIT = 20
|
||||
private const val BACK_PAGINATION_PAGE_SIZE = 50
|
||||
|
||||
class TimelinePresenter @Inject constructor(
|
||||
private val timelineItemsFactory: TimelineItemsFactory,
|
||||
|
|
@ -164,6 +164,6 @@ class TimelinePresenter @Inject constructor(
|
|||
}
|
||||
|
||||
private fun CoroutineScope.paginateBackwards() = launch {
|
||||
timeline.paginateBackwards(backPaginationEventLimit, backPaginationPageSize)
|
||||
timeline.paginateBackwards(BACK_PAGINATION_EVENT_LIMIT, BACK_PAGINATION_PAGE_SIZE)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ fun TimelineView(
|
|||
|
||||
val lazyListState = rememberLazyListState()
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun inReplyToClicked(eventId: EventId) {
|
||||
// TODO implement this logic once we have support to 'jump to event X' in sliding sync
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.vanniktech.emoji.Emoji
|
||||
import com.vanniktech.emoji.google.GoogleEmojiProvider
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
|
|
@ -62,7 +61,7 @@ fun EmojiPicker(
|
|||
val emojiProvider = remember { GoogleEmojiProvider() }
|
||||
val categories = remember { emojiProvider.categories }
|
||||
val pagerState = rememberPagerState()
|
||||
Column (modifier) {
|
||||
Column(modifier) {
|
||||
TabRow(
|
||||
selectedTabIndex = pagerState.currentPage,
|
||||
) {
|
||||
|
|
@ -109,7 +108,8 @@ fun EmojiPicker(
|
|||
Text(
|
||||
text = item.unicode,
|
||||
style = ElementTheme.typography.fontHeadingSmRegular,
|
||||
) }
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import androidx.compose.ui.graphics.Shape
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleState
|
||||
import io.element.android.features.messages.impl.timeline.model.bubble.BubbleStateProvider
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.designsystem.theme.components.Surface
|
||||
|
||||
private val CORNER_RADIUS = 8.dp
|
||||
|
|
|
|||
|
|
@ -117,6 +117,7 @@ private fun TextContent(
|
|||
.height(reactionEmojiLineHeight.toDp()),
|
||||
text = text,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
color = ElementTheme.materialColors.primary
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
@ -126,7 +127,7 @@ private fun IconContent(
|
|||
) = Icon(
|
||||
imageVector = imageVector,
|
||||
contentDescription = stringResource(id = R.string.screen_room_timeline_add_reaction),
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
tint = ElementTheme.materialColors.secondary,
|
||||
modifier = modifier
|
||||
.size(reactionEmojiLineHeight.toDp())
|
||||
)
|
||||
|
|
|
|||
|
|
@ -59,7 +59,6 @@ import androidx.compose.ui.unit.dp
|
|||
import androidx.compose.ui.zIndex
|
||||
import androidx.constraintlayout.compose.ConstrainScope
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import com.google.accompanist.flowlayout.FlowMainAxisAlignment
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
|
||||
|
|
@ -291,7 +290,7 @@ private fun TimelineItemEventRowContent(
|
|||
if (event.reactionsState.reactions.isNotEmpty()) {
|
||||
TimelineItemReactions(
|
||||
reactionsState = event.reactionsState,
|
||||
mainAxisAlignment = if (event.isMine) FlowMainAxisAlignment.End else FlowMainAxisAlignment.Start,
|
||||
isOutgoing = event.isMine,
|
||||
onReactionClicked = onReactionClicked,
|
||||
onMoreReactionsClicked = { onMoreReactionsClicked(event) },
|
||||
modifier = Modifier
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
/*
|
||||
* Copyright (c) 2023 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* 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.element.android.features.messages.impl.timeline.components
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AddReaction
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.MeasureResult
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
|
||||
/**
|
||||
* A flow layout for reactions that will show a collapse/expand button when the layout wraps over a defined number of rows.
|
||||
* It displays an add more button when there are greater than 0 reactions and always displays the reaction and add more button
|
||||
* on the same row (moving them both to a new row if necessary).
|
||||
* @param expandButton The expand button
|
||||
* @param addMoreButton The add more button
|
||||
* @param modifier The modifier to apply to this layout
|
||||
* @param itemSpacing The horizontal spacing between items
|
||||
* @param rowSpacing The vertical spacing between rows
|
||||
* @param expanded Whether the layout should display in expanded or collapsed state
|
||||
* @param rowsBeforeCollapsible The number of rows before the collapse/expand button is shown
|
||||
* @param reactions The reaction buttons
|
||||
*/
|
||||
@Composable
|
||||
fun TimelineItemReactionsLayout(
|
||||
expandButton: @Composable () -> Unit,
|
||||
addMoreButton: @Composable () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
itemSpacing: Dp = 0.dp,
|
||||
rowSpacing: Dp = 0.dp,
|
||||
expanded: Boolean = false,
|
||||
rowsBeforeCollapsible: Int? = 2,
|
||||
reactions: @Composable () -> Unit,
|
||||
) {
|
||||
SubcomposeLayout(modifier) { constraints ->
|
||||
// Given the placeables and returns a structure representing
|
||||
// how they should wrap on to multiple rows given the constraints max width.
|
||||
fun calculateRows(measurables: List<Placeable>): List<List<Placeable>> {
|
||||
val rows = mutableListOf<List<Placeable>>()
|
||||
var currentRow = mutableListOf<Placeable>()
|
||||
var rowX = 0
|
||||
|
||||
measurables.forEach { placeable ->
|
||||
val horizontalSpacing = if (currentRow.isEmpty()) 0 else itemSpacing.toPx().toInt()
|
||||
// If the current view does not fit on this row bump to the next
|
||||
if (rowX + placeable.width > constraints.maxWidth) {
|
||||
rows.add(currentRow)
|
||||
currentRow = mutableListOf()
|
||||
rowX = 0
|
||||
}
|
||||
rowX += horizontalSpacing + placeable.width
|
||||
currentRow.add(placeable)
|
||||
}
|
||||
// If there are items in the current row remember to append it to the returned value
|
||||
if (currentRow.size > 0) {
|
||||
rows.add(currentRow)
|
||||
}
|
||||
return rows
|
||||
}
|
||||
|
||||
// Used to render the collapsed state, this takes the rows inputted and adds the extra button to the last row,
|
||||
// removing only as many trailing reactions as needed to make space for it.
|
||||
fun replaceTrailingItemsWithButtons(rowsIn: List<List<Placeable>>, expandButton: Placeable, addMoreButton: Placeable): List<List<Placeable>> {
|
||||
val rows = rowsIn.toMutableList()
|
||||
val lastRow = rows.last()
|
||||
val buttonsWidth = expandButton.width + itemSpacing.toPx().toInt() + addMoreButton.width
|
||||
var rowX = 0
|
||||
lastRow.forEachIndexed { i, placeable ->
|
||||
val horizontalSpacing = if (i == 0) 0 else itemSpacing.toPx().toInt()
|
||||
rowX += placeable.width + horizontalSpacing
|
||||
if (rowX > constraints.maxWidth - (buttonsWidth + horizontalSpacing)) {
|
||||
val lastRowWithButton = lastRow.take(i) + listOf(expandButton, addMoreButton)
|
||||
rows[rows.size - 1] = lastRowWithButton
|
||||
return rows
|
||||
}
|
||||
}
|
||||
val lastRowWithButton = lastRow + listOf(expandButton, addMoreButton)
|
||||
rows[rows.size - 1] = lastRowWithButton
|
||||
return rows
|
||||
}
|
||||
|
||||
// To prevent the add more and expand buttons from wrapping on to separate lines.
|
||||
// If there is one item on the last line, it moves the expand button down.
|
||||
fun ensureCollapseAndAddMoreButtonsAreOnTheSameRow(rowsIn: List<List<Placeable>>): List<List<Placeable>> {
|
||||
val lastRow = rowsIn.last().toMutableList()
|
||||
if (lastRow.size != 1) {
|
||||
return rowsIn
|
||||
}
|
||||
val rows = rowsIn.toMutableList()
|
||||
val secondLastRow = rows[rows.size - 2].toMutableList()
|
||||
val expandButtonPlaceable = secondLastRow.removeLast()
|
||||
lastRow.add(0, expandButtonPlaceable)
|
||||
rows[rows.size - 2] = secondLastRow
|
||||
rows[rows.size - 1] = lastRow
|
||||
return rows
|
||||
}
|
||||
|
||||
/// Given a list of rows place them in the layout.
|
||||
fun layoutRows(rows: List<List<Placeable>>): MeasureResult {
|
||||
var width = 0
|
||||
var height = 0
|
||||
val placeables = rows.mapIndexed { i, row ->
|
||||
var rowX = 0
|
||||
var rowHeight = 0
|
||||
val verticalSpacing = if (i == 0) 0 else rowSpacing.toPx().toInt()
|
||||
val rowWithPoints = row.mapIndexed { j, placeable ->
|
||||
val horizontalSpacing = if (j == 0) 0 else itemSpacing.toPx().toInt()
|
||||
val point = IntOffset(rowX + horizontalSpacing, height + verticalSpacing)
|
||||
rowX += placeable.width + horizontalSpacing
|
||||
rowHeight = maxOf(rowHeight, placeable.height)
|
||||
Pair(placeable, point)
|
||||
}
|
||||
height += rowHeight + verticalSpacing
|
||||
width = maxOf(width, rowX)
|
||||
rowWithPoints
|
||||
}.flatten()
|
||||
|
||||
return layout(width = width, height = height) {
|
||||
placeables.forEach {
|
||||
val (placeable, origin) = it
|
||||
placeable.placeRelative(origin.x, origin.y)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val reactionsPlaceables = subcompose(0, reactions).map { it.measure(constraints) }
|
||||
if (reactionsPlaceables.isEmpty()) {
|
||||
return@SubcomposeLayout layoutRows(listOf())
|
||||
}
|
||||
val addMorePlaceable = subcompose(1, addMoreButton).first().measure(constraints)
|
||||
val expandPlaceable = subcompose(2, expandButton).first().measure(constraints)
|
||||
|
||||
// Calculate the layout of the rows with the reactions button and add more button
|
||||
val reactionsAndAddMore = calculateRows(reactionsPlaceables + listOf(addMorePlaceable))
|
||||
// If we have extended beyond the defined number of rows we are showing the expand/collapse ui
|
||||
if (rowsBeforeCollapsible?.let { reactionsAndAddMore.size > it } == true) {
|
||||
if (expanded) {
|
||||
// Show all subviews with the add more button at the end
|
||||
var reactionsAndButtons = calculateRows(reactionsPlaceables + listOf(expandPlaceable, addMorePlaceable))
|
||||
reactionsAndButtons = ensureCollapseAndAddMoreButtonsAreOnTheSameRow(reactionsAndButtons)
|
||||
layoutRows(reactionsAndButtons)
|
||||
} else {
|
||||
// Truncate to `rowsBeforeCollapsible` number of rows and replace the reactions at the end of the last row with the buttons
|
||||
val collapsedRows = reactionsAndAddMore.take(rowsBeforeCollapsible)
|
||||
val collapsedRowsWithButtons = replaceTrailingItemsWithButtons(collapsedRows, expandPlaceable, addMorePlaceable)
|
||||
layoutRows(collapsedRowsWithButtons)
|
||||
}
|
||||
} else {
|
||||
// Otherwise we are just showing all items without the expand button
|
||||
layoutRows(reactionsAndAddMore)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
internal fun TimelineItemReactionsLayoutPreview() = ElementPreview {
|
||||
TimelineItemReactionsLayout(
|
||||
expanded = false,
|
||||
expandButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = stringResource(id = R.string.screen_room_timeline_less_reactions)
|
||||
),
|
||||
onClick = { },
|
||||
)
|
||||
},
|
||||
addMoreButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = {}
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
io.element.android.features.messages.impl.timeline.aTimelineItemReactions(count = 18).reactions.forEach {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(
|
||||
it
|
||||
),
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -19,18 +19,16 @@ package io.element.android.features.messages.impl.timeline.components
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.AddReaction
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.platform.LocalLayoutDirection
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.flowlayout.FlowMainAxisAlignment
|
||||
import com.google.accompanist.flowlayout.FlowRow
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.features.messages.impl.timeline.aTimelineItemReactions
|
||||
import io.element.android.features.messages.impl.timeline.model.AggregatedReaction
|
||||
|
|
@ -38,162 +36,119 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItemReac
|
|||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
/**
|
||||
* The maximum number of items that can be displayed before some items will be hidden
|
||||
*
|
||||
* TODO The threshold should be based on the number of rows, rather than items.
|
||||
* Once items would spill onto a third row, they should be hidden.
|
||||
* Note this could be particularly worthwhile to handle reactions that are
|
||||
* longer than a single character (as annotation keys are free text).
|
||||
*/
|
||||
private const val COLLAPSE_ITEMS_THRESHOLD = 8
|
||||
|
||||
@Composable
|
||||
fun TimelineItemReactions(
|
||||
reactionsState: TimelineItemReactions,
|
||||
mainAxisAlignment: FlowMainAxisAlignment,
|
||||
isOutgoing: Boolean,
|
||||
onReactionClicked: (emoji: String) -> Unit,
|
||||
onMoreReactionsClicked: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var expanded: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
val reactions by remember(reactionsState, expanded) {
|
||||
derivedStateOf {
|
||||
val numToDisplay = if (expanded) {
|
||||
reactionsState.reactions.count()
|
||||
} else {
|
||||
COLLAPSE_ITEMS_THRESHOLD
|
||||
}
|
||||
reactionsState.reactions.take(numToDisplay).toPersistentList()
|
||||
}
|
||||
// In LTR languages we want an incoming message's reactions to be LRT and outgoing to be RTL.
|
||||
// For RTL languages it should be the opposite.
|
||||
val reactionsLayoutDirection = if (!isOutgoing) LocalLayoutDirection.current
|
||||
else if (LocalLayoutDirection.current == LayoutDirection.Ltr)
|
||||
LayoutDirection.Rtl
|
||||
else
|
||||
LayoutDirection.Ltr
|
||||
|
||||
CompositionLocalProvider(LocalLayoutDirection provides reactionsLayoutDirection) {
|
||||
TimelineItemReactionsView(
|
||||
modifier = modifier,
|
||||
reactions = reactionsState.reactions,
|
||||
expanded = expanded,
|
||||
onReactionClick = onReactionClicked,
|
||||
onMoreReactionsClick = onMoreReactionsClicked,
|
||||
onToggleExpandClick = { expanded = !expanded },
|
||||
)
|
||||
}
|
||||
|
||||
val expandableState by remember {
|
||||
derivedStateOf {
|
||||
if (expanded) {
|
||||
ExpandableState.Expanded
|
||||
} else {
|
||||
val hiddenItems = reactionsState.reactions.count() - reactions.count()
|
||||
if (hiddenItems > 0) {
|
||||
ExpandableState.Collapsed(hidden = hiddenItems)
|
||||
} else {
|
||||
ExpandableState.None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TimelineItemReactionsView(
|
||||
modifier = modifier,
|
||||
reactions = reactions,
|
||||
expandableState = expandableState,
|
||||
mainAxisAlignment = mainAxisAlignment,
|
||||
onReactionClick = onReactionClicked,
|
||||
onMoreReactionsClick = onMoreReactionsClicked,
|
||||
onExpandClick = { expanded = true },
|
||||
onCollapseClick = { expanded = false }
|
||||
)
|
||||
}
|
||||
|
||||
private sealed class ExpandableState {
|
||||
object None: ExpandableState()
|
||||
data class Collapsed(val hidden: Int): ExpandableState()
|
||||
object Expanded : ExpandableState()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TimelineItemReactionsView(
|
||||
reactions: ImmutableList<AggregatedReaction>,
|
||||
expandableState: ExpandableState,
|
||||
mainAxisAlignment: FlowMainAxisAlignment,
|
||||
expanded: Boolean,
|
||||
onReactionClick: (emoji: String) -> Unit,
|
||||
onMoreReactionsClick: () -> Unit,
|
||||
onExpandClick: () -> Unit,
|
||||
onCollapseClick: () -> Unit,
|
||||
onToggleExpandClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) =
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
mainAxisSpacing = 4.dp,
|
||||
crossAxisSpacing = 4.dp,
|
||||
mainAxisAlignment = mainAxisAlignment,
|
||||
) {
|
||||
) = TimelineItemReactionsLayout(
|
||||
modifier = modifier,
|
||||
itemSpacing = 4.dp,
|
||||
rowSpacing = 4.dp,
|
||||
expanded = expanded,
|
||||
expandButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = stringResource(id = if (expanded) R.string.screen_room_reactions_show_less else R.string.screen_room_reactions_show_more)
|
||||
),
|
||||
onClick = onToggleExpandClick,
|
||||
)
|
||||
},
|
||||
addMoreButton = {
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = onMoreReactionsClick
|
||||
)
|
||||
},
|
||||
reactions = {
|
||||
reactions.forEach { reaction ->
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Reaction(reaction = reaction),
|
||||
onClick = { onReactionClick(reaction.key) }
|
||||
)
|
||||
}
|
||||
when (expandableState) {
|
||||
ExpandableState.Expanded ->
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = stringResource(id = R.string.screen_room_timeline_less_reactions)
|
||||
),
|
||||
onClick = onCollapseClick,
|
||||
)
|
||||
is ExpandableState.Collapsed -> {
|
||||
val hidden = expandableState.hidden
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Text(
|
||||
text = pluralStringResource(id = R.plurals.screen_room_timeline_more_reactions, hidden, hidden)
|
||||
),
|
||||
onClick = onExpandClick,
|
||||
)
|
||||
}
|
||||
ExpandableState.None -> {
|
||||
// No expand or collapse action available
|
||||
}
|
||||
}
|
||||
MessagesReactionButton(
|
||||
content = MessagesReactionsButtonContent.Icon(Icons.Outlined.AddReaction),
|
||||
onClick = onMoreReactionsClick
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 1).reactions,
|
||||
expandableState = ExpandableState.None,
|
||||
reactions = aTimelineItemReactions(count = 1).reactions
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewCollapsedPreview() = ElementPreview {
|
||||
fun TimelineItemReactionsViewFewPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 3).reactions,
|
||||
expandableState = ExpandableState.Collapsed(hidden = 7),
|
||||
reactions = aTimelineItemReactions(count = 3).reactions
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewExpandedPreview() = ElementPreview {
|
||||
fun TimelineItemReactionsViewIncomingPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 10).reactions,
|
||||
expandableState = ExpandableState.Expanded,
|
||||
reactions = aTimelineItemReactions(count = 18).reactions
|
||||
)
|
||||
}
|
||||
|
||||
@DayNightPreviews
|
||||
@Composable
|
||||
fun TimelineItemReactionsViewOutgoingPreview() = ElementPreview {
|
||||
ContentToPreview(
|
||||
reactions = aTimelineItemReactions(count = 18).reactions,
|
||||
isOutgoing = true
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentToPreview(
|
||||
reactions: ImmutableList<AggregatedReaction>,
|
||||
expandableState: ExpandableState
|
||||
isOutgoing: Boolean = false
|
||||
) {
|
||||
TimelineItemReactionsView(
|
||||
reactions = reactions,
|
||||
expandableState = expandableState,
|
||||
mainAxisAlignment = FlowMainAxisAlignment.Center,
|
||||
onReactionClick = {},
|
||||
onMoreReactionsClick = {},
|
||||
onExpandClick = {},
|
||||
onCollapseClick = {}
|
||||
TimelineItemReactions(
|
||||
reactionsState = TimelineItemReactions(
|
||||
reactions
|
||||
),
|
||||
isOutgoing = isOutgoing,
|
||||
onReactionClicked = {},
|
||||
onMoreReactionsClicked = {},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ fun TimelineItem.Event.toExtraPadding(): ExtraPadding {
|
|||
fun ExtraPadding.getStr(fontSize: TextUnit): String {
|
||||
if (nbChars == 0) return ""
|
||||
val timestampFontSize = ElementTheme.typography.fontBodyXsRegular.fontSize // 11.sp
|
||||
val nbOfSpaces = ((timestampFontSize.value / fontSize.value) * nbChars).toInt() + 1
|
||||
val nbOfSpaces = (timestampFontSize.value / fontSize.value * nbChars).toInt() + 1
|
||||
// A space and some unbreakable spaces
|
||||
return " " + "\u00A0".repeat(nbOfSpaces)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
|
||||
@Composable
|
||||
fun TimelineItemEncryptedView(
|
||||
content: TimelineItemEncryptedContent,
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemEncryptedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
|
||||
@Composable
|
||||
fun TimelineItemRedactedView(
|
||||
content: TimelineItemRedactedContent,
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemRedactedContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
|
||||
@Composable
|
||||
fun TimelineItemUnknownView(
|
||||
content: TimelineItemUnknownContent,
|
||||
@Suppress("UNUSED_PARAMETER") content: TimelineItemUnknownContent,
|
||||
extraPadding: ExtraPadding,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package io.element.android.features.messages.impl.timeline.components.event
|
|||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import androidx.compose.ui.draw.clip
|
|||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ import org.jsoup.nodes.Element
|
|||
import org.jsoup.nodes.Node
|
||||
import org.jsoup.nodes.TextNode
|
||||
|
||||
private const val chipId = "chip"
|
||||
private const val CHIP_ID = "chip"
|
||||
|
||||
@Composable
|
||||
fun HtmlDocument(
|
||||
|
|
@ -351,7 +351,7 @@ private fun HtmlMxReply(
|
|||
Surface(
|
||||
modifier = modifier
|
||||
.padding(bottom = 4.dp)
|
||||
.offset(x = -(8.dp)),
|
||||
.offset(x = (-8).dp),
|
||||
color = MaterialTheme.colorScheme.background,
|
||||
shape = shape,
|
||||
) {
|
||||
|
|
@ -544,17 +544,27 @@ private fun AnnotatedString.Builder.appendLink(link: Element) {
|
|||
pop()
|
||||
}
|
||||
is PermalinkData.RoomEmailInviteLink -> {
|
||||
appendInlineContent(chipId, link.ownText())
|
||||
safeAppendInlineContent(CHIP_ID, link.ownText())
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
appendInlineContent(chipId, link.ownText())
|
||||
safeAppendInlineContent(CHIP_ID, link.ownText())
|
||||
}
|
||||
is PermalinkData.UserLink -> {
|
||||
appendInlineContent(chipId, link.ownText())
|
||||
safeAppendInlineContent(CHIP_ID, link.ownText())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun AnnotatedString.Builder.safeAppendInlineContent(chipId: String, ownText: String) {
|
||||
if (ownText.isEmpty()) {
|
||||
// alternateText cannot be empty and default parameter value is private,
|
||||
// so just omit the second param here.
|
||||
appendInlineContent(chipId)
|
||||
} else {
|
||||
appendInlineContent(chipId, ownText)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HtmlText(
|
||||
text: AnnotatedString,
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -165,6 +164,7 @@ internal fun RetrySendMessageMenuPreviewDark(@PreviewParameter(RetrySendMenuStat
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun ContentToPreview(state: RetrySendMenuState) {
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.libraries.designsystem.preview.DayNightPreviews
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import androidx.compose.material3.MaterialTheme
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import javax.inject.Inject
|
|||
|
||||
class TimelineItemContentFailedToParseMessageFactory @Inject constructor() {
|
||||
|
||||
fun create(failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent {
|
||||
fun create(@Suppress("UNUSED_PARAMETER") failedToParseMessageLike: FailedToParseMessageLikeContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ import javax.inject.Inject
|
|||
|
||||
class TimelineItemContentFailedToParseStateFactory @Inject constructor() {
|
||||
|
||||
@Suppress("UNUSED_PARAMETER")
|
||||
fun create(failedToParseState: FailedToParseStateContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import javax.inject.Inject
|
|||
|
||||
class TimelineItemContentRedactedFactory @Inject constructor() {
|
||||
|
||||
fun create(content: RedactedContent): TimelineItemEventContent {
|
||||
fun create(@Suppress("UNUSED_PARAMETER") content: RedactedContent): TimelineItemEventContent {
|
||||
return TimelineItemRedactedContent
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import javax.inject.Inject
|
|||
|
||||
class TimelineItemContentStickerFactory @Inject constructor() {
|
||||
|
||||
fun create(content: StickerContent): TimelineItemEventContent {
|
||||
fun create(@Suppress("UNUSED_PARAMETER") content: StickerContent): TimelineItemEventContent {
|
||||
return TimelineItemUnknownContent
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<item quantity="other">"%1$d changements dans la conversation"</item>
|
||||
</plurals>
|
||||
<plurals name="screen_room_timeline_more_reactions">
|
||||
<item quantity="one"></item>
|
||||
<item quantity="one">"1 de plus"</item>
|
||||
<item quantity="other">"%1$d de plus"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_attachment_source_camera">"Appareil photo"</string>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
<item quantity="other">"%1$d zmien miestnosti"</item>
|
||||
</plurals>
|
||||
<plurals name="screen_room_timeline_more_reactions">
|
||||
<item quantity="one"></item>
|
||||
<item quantity="one">"%1$d ďalší"</item>
|
||||
<item quantity="few">"%1$d ďalšie"</item>
|
||||
<item quantity="other">"%1$d ďalších"</item>
|
||||
</plurals>
|
||||
|
|
@ -22,6 +22,17 @@
|
|||
<string name="screen_room_invite_again_alert_title">"V tomto rozhovore ste sami"</string>
|
||||
<string name="screen_room_message_copied">"Správa skopírovaná"</string>
|
||||
<string name="screen_room_no_permission_to_post">"Nemáte povolenie uverejňovať príspevky v tejto miestnosti"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom">"Povoliť vlastné nastavenie"</string>
|
||||
<string name="screen_room_notification_settings_allow_custom_footnote">"Zapnutím tohto nastavenia sa prepíše vaše predvolené nastavenie"</string>
|
||||
<string name="screen_room_notification_settings_custom_settings_title">"Upozorniť ma v tejto konverzácii na"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote">"Môžete to zmeniť vo svojich %1$s."</string>
|
||||
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"všeobecných nastaveniach"</string>
|
||||
<string name="screen_room_notification_settings_default_setting_title">"Predvolené nastavenie"</string>
|
||||
<string name="screen_room_notification_settings_error_loading_settings">"Pri načítavaní nastavení oznámení došlo k chybe."</string>
|
||||
<string name="screen_room_notification_settings_error_restoring_default">"Nepodarilo sa obnoviť predvolený režim, skúste to prosím znova."</string>
|
||||
<string name="screen_room_notification_settings_error_setting_mode">"Nepodarilo sa nastaviť režim, skúste to prosím znova."</string>
|
||||
<string name="screen_room_notification_settings_mode_all_messages">"Všetky správy"</string>
|
||||
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Iba zmienky a kľúčové slová"</string>
|
||||
<string name="screen_room_reactions_show_less">"Zobraziť menej"</string>
|
||||
<string name="screen_room_reactions_show_more">"Zobraziť viac"</string>
|
||||
<string name="screen_room_retry_send_menu_send_again_action">"Odoslať znova"</string>
|
||||
|
|
|
|||
|
|
@ -17,10 +17,9 @@
|
|||
package io.element.android.features.messages
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.collect.Iterables.skip
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.features.messages.fixtures.aMessageEvent
|
||||
|
|
@ -46,8 +45,6 @@ import io.element.android.features.messages.utils.messagesummary.FakeMessageSumm
|
|||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
|
|
@ -81,10 +78,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.roomId).isEqualTo(A_ROOM_ID)
|
||||
}
|
||||
|
|
@ -95,14 +92,13 @@ class MessagesPresenterTest {
|
|||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createMessagePresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
|
||||
assertThat(room.myReactions.count()).isEqualTo(1)
|
||||
|
||||
// No crashes when sending a reaction failed
|
||||
room.givenToggleReactionResult(Result.failure(IllegalStateException("Failed to send reaction")))
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
|
||||
|
|
@ -116,10 +112,10 @@ class MessagesPresenterTest {
|
|||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createMessagePresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", AN_EVENT_ID))
|
||||
assertThat(room.myReactions.count()).isEqualTo(1)
|
||||
|
|
@ -133,10 +129,10 @@ class MessagesPresenterTest {
|
|||
fun `present - handle action forward`() = runTest {
|
||||
val navigator = FakeMessagesNavigator()
|
||||
val presenter = createMessagePresenter(navigator = navigator)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -149,10 +145,10 @@ class MessagesPresenterTest {
|
|||
val clipboardHelper = FakeClipboardHelper()
|
||||
val event = aMessageEvent()
|
||||
val presenter = createMessagePresenter(clipboardHelper = clipboardHelper)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -163,13 +159,12 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action reply`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -179,10 +174,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action reply to an event with no id does nothing`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -194,10 +189,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action reply to an image media message`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemImageContent(
|
||||
|
|
@ -214,7 +209,6 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
|
|
@ -226,10 +220,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action reply to a video media message`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemVideoContent(
|
||||
|
|
@ -247,7 +241,6 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
|
|
@ -259,10 +252,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action reply to a file media message`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemFileContent(
|
||||
|
|
@ -275,7 +268,6 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
|
|
@ -287,13 +279,12 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle action edit`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
||||
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java)
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -305,10 +296,10 @@ class MessagesPresenterTest {
|
|||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||
val matrixRoom = FakeMatrixRoom()
|
||||
val presenter = createMessagePresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, aMessageEvent()))
|
||||
assertThat(matrixRoom.redactEventEventIdParam).isEqualTo(AN_EVENT_ID)
|
||||
|
|
@ -320,10 +311,10 @@ class MessagesPresenterTest {
|
|||
fun `present - handle action report content`() = runTest {
|
||||
val navigator = FakeMessagesNavigator()
|
||||
val presenter = createMessagePresenter(navigator = navigator)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -334,10 +325,10 @@ class MessagesPresenterTest {
|
|||
@Test
|
||||
fun `present - handle dismiss action`() = runTest {
|
||||
val presenter = createMessagePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.Dismiss)
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -348,10 +339,10 @@ class MessagesPresenterTest {
|
|||
fun `present - handle action show developer info`() = runTest {
|
||||
val navigator = FakeMessagesNavigator()
|
||||
val presenter = createMessagePresenter(navigator = navigator)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Developer, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
|
@ -363,20 +354,17 @@ class MessagesPresenterTest {
|
|||
fun `present - shows prompt to reinvite users in DM`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
|
||||
// Initially the composer doesn't have focus, so we don't show the alert
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
|
||||
// When the input field is focused we show the alert
|
||||
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isTrue()
|
||||
|
||||
// If it's dismissed then we stop showing the alert
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel))
|
||||
val dismissedState = awaitItem()
|
||||
|
|
@ -388,13 +376,12 @@ class MessagesPresenterTest {
|
|||
fun `present - doesn't show reinvite prompt in non-direct room`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
|
||||
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isFalse()
|
||||
|
|
@ -405,13 +392,12 @@ class MessagesPresenterTest {
|
|||
fun `present - doesn't show reinvite prompt if other party is present`() = runTest {
|
||||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
|
||||
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isFalse()
|
||||
|
|
@ -430,17 +416,16 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
|
||||
|
||||
skipItems(1)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isSuccess()).isTrue()
|
||||
assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
|
||||
|
|
@ -460,17 +445,16 @@ class MessagesPresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
|
||||
|
||||
skipItems(1)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isSuccess()).isTrue()
|
||||
assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2)
|
||||
|
|
@ -482,17 +466,16 @@ class MessagesPresenterTest {
|
|||
val room = FakeMatrixRoom(sessionId = A_SESSION_ID)
|
||||
room.givenRoomMembersState(MatrixRoomMembersState.Unknown)
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
|
||||
|
||||
skipItems(1)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isFailure()).isTrue()
|
||||
}
|
||||
|
|
@ -511,16 +494,16 @@ class MessagesPresenterTest {
|
|||
)
|
||||
room.givenInviteUserResult(Result.failure(Throwable("Oops!")))
|
||||
val presenter = createMessagePresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
skipItems(1)
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Invite))
|
||||
skipItems(1)
|
||||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.inviteProgress.isLoading()).isTrue()
|
||||
|
||||
val newState = awaitItem()
|
||||
assertThat(newState.inviteProgress.isFailure()).isTrue()
|
||||
}
|
||||
|
|
@ -531,10 +514,10 @@ class MessagesPresenterTest {
|
|||
val matrixRoom = FakeMatrixRoom()
|
||||
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true))
|
||||
val presenter = createMessagePresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -544,12 +527,12 @@ class MessagesPresenterTest {
|
|||
val matrixRoom = FakeMatrixRoom()
|
||||
matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false))
|
||||
val presenter = createMessagePresenter(matrixRoom = matrixRoom)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
// Default value
|
||||
assertThat(awaitItem().userHasPermissionToSendMessage).isTrue()
|
||||
skipItems(1)
|
||||
skipItems(2)
|
||||
assertThat(awaitItem().userHasPermissionToSendMessage).isFalse()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.actionlist
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -30,7 +30,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.test.A_MESSAGE
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -41,7 +40,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -52,7 +51,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for message from me redacted`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -77,7 +76,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for message from others redacted`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -102,7 +101,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for others message`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -134,7 +133,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for my message`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -167,7 +166,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for a media item`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -198,7 +197,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for a state item in debug build`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = true)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -227,7 +226,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute for a state item in non-debuggable build`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -255,7 +254,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute message in non-debuggable build`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -287,7 +286,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute message with no actions`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -314,7 +313,7 @@ class ActionListPresenterTest {
|
|||
@Test
|
||||
fun `present - compute not sent message`() = runTest {
|
||||
val presenter = anActionListPresenter(isBuildDebuggable = false)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
package io.element.android.features.messages.attachments
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -55,7 +55,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = anAttachmentsPreviewPresenter(room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -77,7 +77,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
val failure = MediaPreProcessor.Failure(null)
|
||||
room.givenSendMediaResult(Result.failure(failure))
|
||||
val presenter = anAttachmentsPreviewPresenter(room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -86,7 +86,7 @@ class AttachmentsPreviewPresenterTest {
|
|||
val loadingState = awaitItem()
|
||||
assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing)
|
||||
val failureState = awaitItem()
|
||||
assertThat(failureState.sendActionState).isEqualTo((SendActionState.Failure(failure)))
|
||||
assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure))
|
||||
assertThat(room.sendMediaCount).isEqualTo(0)
|
||||
failureState.eventSink(AttachmentsPreviewEvents.ClearSendState)
|
||||
val clearedState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@ import io.element.android.features.messages.impl.attachments.Attachment
|
|||
import io.element.android.features.messages.impl.media.local.LocalMedia
|
||||
import io.element.android.features.messages.impl.media.local.MediaInfo
|
||||
import io.element.android.features.messages.impl.media.local.anImageInfo
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
|
||||
fun aLocalMedia(
|
||||
uri: Uri,
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.forward
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -40,7 +40,7 @@ class ForwardMessagesPresenterTests {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -60,7 +60,7 @@ class ForwardMessagesPresenterTests {
|
|||
@Test
|
||||
fun `present - toggle search active`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -81,7 +81,7 @@ class ForwardMessagesPresenterTests {
|
|||
}
|
||||
val client = FakeMatrixClient(roomSummaryDataSource = roomSummaryDataSource)
|
||||
val presenter = aPresenter(client = client)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -96,7 +96,7 @@ class ForwardMessagesPresenterTests {
|
|||
@Test
|
||||
fun `present - select a room and forward successful`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -123,7 +123,7 @@ class ForwardMessagesPresenterTests {
|
|||
fun `present - select a room and forward failed, then clear`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = aPresenter(fakeMatrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -151,7 +151,7 @@ class ForwardMessagesPresenterTests {
|
|||
@Test
|
||||
fun `present - select and remove a room`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
package io.element.android.features.messages.media.viewer
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -50,7 +50,7 @@ class MediaViewerPresenterTest {
|
|||
val mediaLoader = FakeMediaLoader()
|
||||
val mediaActions = FakeLocalMediaActions()
|
||||
val presenter = aMediaViewerPresenter(mediaLoader, mediaActions)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
var state = awaitItem()
|
||||
|
|
@ -71,7 +71,7 @@ class MediaViewerPresenterTest {
|
|||
val mediaActions = FakeLocalMediaActions()
|
||||
val snackbarDispatcher = SnackbarDispatcher()
|
||||
val presenter = aMediaViewerPresenter(mediaLoader, mediaActions, snackbarDispatcher)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
var state = awaitItem()
|
||||
|
|
@ -117,7 +117,7 @@ class MediaViewerPresenterTest {
|
|||
val mediaLoader = FakeMediaLoader()
|
||||
val mediaActions = FakeLocalMediaActions()
|
||||
val presenter = aMediaViewerPresenter(mediaLoader, mediaActions)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
mediaLoader.shouldFail = true
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.report
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -36,7 +36,7 @@ class ReportMessagePresenterTests {
|
|||
@Test
|
||||
fun `presenter - initial state`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -49,7 +49,7 @@ class ReportMessagePresenterTests {
|
|||
@Test
|
||||
fun `presenter - update reason`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -63,7 +63,7 @@ class ReportMessagePresenterTests {
|
|||
@Test
|
||||
fun `presenter - toggle block user`() = runTest {
|
||||
val presenter = aPresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -81,7 +81,7 @@ class ReportMessagePresenterTests {
|
|||
fun `presenter - handle successful report and block user`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = aPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -98,7 +98,7 @@ class ReportMessagePresenterTests {
|
|||
fun `presenter - handle successful report`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = aPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -115,7 +115,7 @@ class ReportMessagePresenterTests {
|
|||
givenReportContentResult(Result.failure(Exception("Failed to report content")))
|
||||
}
|
||||
val presenter = aPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
package io.element.android.features.messages.textcomposer
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
|
|
@ -68,7 +68,7 @@ class MessageComposerPresenterTest {
|
|||
givenResult(mockk()) // Uri is not available in JVM, so the only way to have a non-null Uri is using Mockk
|
||||
}
|
||||
private val featureFlagService = FakeFeatureFlagService(
|
||||
mapOf(FeatureFlags.ShowMediaUploadingFlow.key to true)
|
||||
mapOf(FeatureFlags.LocationSharing.key to true)
|
||||
)
|
||||
private val mediaPreProcessor = FakeMediaPreProcessor()
|
||||
private val snackbarDispatcher = SnackbarDispatcher()
|
||||
|
|
@ -78,14 +78,16 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.isFullScreen).isFalse()
|
||||
assertThat(initialState.text).isEqualTo("")
|
||||
assertThat(initialState.mode).isEqualTo(MessageComposerMode.Normal(""))
|
||||
assertThat(initialState.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(initialState.canShareLocation).isTrue()
|
||||
assertThat(initialState.attachmentsState).isEqualTo(AttachmentsState.None)
|
||||
assertThat(initialState.isSendButtonVisible).isFalse()
|
||||
}
|
||||
|
|
@ -94,9 +96,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - toggle fullscreen`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.ToggleFullScreenState)
|
||||
val fullscreenState = awaitItem()
|
||||
|
|
@ -110,9 +113,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - change message`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE))
|
||||
val withMessageState = awaitItem()
|
||||
|
|
@ -128,9 +132,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - change mode to edit`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
var state = awaitItem()
|
||||
val mode = anEditMode()
|
||||
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
|
||||
|
|
@ -146,9 +151,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - change mode to reply`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
var state = awaitItem()
|
||||
val mode = aReplyMode()
|
||||
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
|
||||
|
|
@ -163,9 +169,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - change mode to quote`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
var state = awaitItem()
|
||||
val mode = aQuoteMode()
|
||||
state.eventSink.invoke(MessageComposerEvents.SetMode(mode))
|
||||
|
|
@ -180,9 +187,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - send message`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.UpdateText(A_MESSAGE))
|
||||
val withMessageState = awaitItem()
|
||||
|
|
@ -202,9 +210,10 @@ class MessageComposerPresenterTest {
|
|||
this,
|
||||
fakeMatrixRoom,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.text).isEqualTo("")
|
||||
val mode = anEditMode()
|
||||
|
|
@ -233,9 +242,10 @@ class MessageComposerPresenterTest {
|
|||
this,
|
||||
fakeMatrixRoom,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.text).isEqualTo("")
|
||||
val mode = anEditMode(eventId = null, transactionId = A_TRANSACTION_ID)
|
||||
|
|
@ -264,9 +274,10 @@ class MessageComposerPresenterTest {
|
|||
this,
|
||||
fakeMatrixRoom,
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.text).isEqualTo("")
|
||||
val mode = aReplyMode()
|
||||
|
|
@ -291,9 +302,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - Open attachments menu`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showAttachmentSourcePicker).isEqualTo(false)
|
||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||
|
|
@ -304,9 +316,10 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - Dismiss attachments menu`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||
skipItems(1)
|
||||
|
|
@ -338,9 +351,10 @@ class MessageComposerPresenterTest {
|
|||
)
|
||||
)
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
|
||||
val previewingState = awaitItem()
|
||||
|
|
@ -372,9 +386,10 @@ class MessageComposerPresenterTest {
|
|||
)
|
||||
)
|
||||
)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
|
||||
val previewingState = awaitItem()
|
||||
|
|
@ -390,9 +405,10 @@ class MessageComposerPresenterTest {
|
|||
givenResult(null) // Simulate a user canceling the flow
|
||||
givenMimeType(MimeTypes.Images)
|
||||
}
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery)
|
||||
// No crashes here, otherwise it fails
|
||||
|
|
@ -410,9 +426,10 @@ class MessageComposerPresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles)
|
||||
val sendingState = awaitItem()
|
||||
|
|
@ -431,9 +448,10 @@ class MessageComposerPresenterTest {
|
|||
fun `present - Take photo`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera)
|
||||
val previewingState = awaitItem()
|
||||
|
|
@ -447,9 +465,10 @@ class MessageComposerPresenterTest {
|
|||
fun `present - Record video`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera)
|
||||
val previewingState = awaitItem()
|
||||
|
|
@ -464,9 +483,10 @@ class MessageComposerPresenterTest {
|
|||
givenSendMediaResult(Result.failure(Exception()))
|
||||
}
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles)
|
||||
val sendingState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.timeline
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -42,7 +42,7 @@ class TimelinePresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -55,7 +55,7 @@ class TimelinePresenterTest {
|
|||
@Test
|
||||
fun `present - load more`() = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -74,7 +74,7 @@ class TimelinePresenterTest {
|
|||
@Test
|
||||
fun `present - set highlighted event`() = runTest {
|
||||
val presenter = createTimelinePresenter()
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -97,7 +97,7 @@ class TimelinePresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createTimelinePresenter(timeline)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
|
||||
|
|
@ -121,7 +121,7 @@ class TimelinePresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createTimelinePresenter(timeline)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
|
||||
|
|
@ -145,7 +145,7 @@ class TimelinePresenterTest {
|
|||
)
|
||||
)
|
||||
val presenter = createTimelinePresenter(timeline)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(timeline.sendReadReceiptCount).isEqualTo(0)
|
||||
|
|
@ -165,7 +165,7 @@ class TimelinePresenterTest {
|
|||
fun `present - covers hasNewItems scenarios`() = runTest {
|
||||
val timeline = FakeMatrixTimeline()
|
||||
val presenter = createTimelinePresenter(timeline)
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.timeline.components.customreaction
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -32,7 +32,7 @@ class CustomReactionPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle selecting and de-selecting an event`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package io.element.android.features.messages.timeline.components.retrysendmenu
|
||||
|
||||
import app.cash.molecule.RecompositionClock
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -35,7 +35,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle event selected`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -48,7 +48,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle dismiss`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -63,7 +63,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle resend with transactionId`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -79,7 +79,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle resend without transactionId`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -96,7 +96,7 @@ class RetrySendMenuPresenterTests {
|
|||
@Test
|
||||
fun `present - handle resend with error`() = runTest {
|
||||
room.givenRetrySendMessageResult(Result.failure(IllegalStateException("An error")))
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -112,7 +112,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle remove failed message with transactionId`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -128,7 +128,7 @@ class RetrySendMenuPresenterTests {
|
|||
|
||||
@Test
|
||||
fun `present - handle remove failed message without transactionId`() = runTest {
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
@ -145,7 +145,7 @@ class RetrySendMenuPresenterTests {
|
|||
@Test
|
||||
fun `present - handle remove failed message with error`() = runTest {
|
||||
room.givenRetrySendMessageResult(Result.failure(IllegalStateException("An error")))
|
||||
moleculeFlow(RecompositionClock.Immediate) {
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue