Merge branch 'develop' into julioromano/poll_history_entry_point

This commit is contained in:
ganfra 2023-12-13 17:22:55 +01:00
commit 863d156e4d
738 changed files with 9387 additions and 1581 deletions

View file

@ -33,6 +33,7 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import kotlin.coroutines.coroutineContext
class AttachmentsPreviewPresenter @AssistedInject constructor(
@ -114,6 +115,7 @@ class AttachmentsPreviewPresenter @AssistedInject constructor(
sendActionState.value = SendActionState.Done
},
onFailure = { error ->
Timber.e(error, "Failed to send attachment")
if (error is CancellationException) {
throw error
} else {

View file

@ -72,6 +72,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
import kotlin.coroutines.coroutineContext
import kotlin.time.Duration.Companion.seconds
@ -432,6 +433,7 @@ class MessageComposerPresenter @Inject constructor(
attachmentState.value = AttachmentsState.None
}
.onFailure { cause ->
Timber.e(cause, "Failed to send attachment")
attachmentState.value = AttachmentsState.None
if (cause is CancellationException) {
throw cause

View file

@ -175,6 +175,9 @@ class TimelinePresenter @AssistedInject constructor(
}
return TimelineState(
timelineRoomInfo = TimelineRoomInfo(
isDirect = room.isDirect
),
highlightedEventId = highlightedEventId.value,
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
paginationState = paginationState,

View file

@ -27,6 +27,7 @@ import kotlinx.collections.immutable.ImmutableList
@Immutable
data class TimelineState(
val timelineItems: ImmutableList<TimelineItem>,
val timelineRoomInfo: TimelineRoomInfo,
val showReadReceipts: Boolean,
val highlightedEventId: EventId?,
val userHasPermissionToSendMessage: Boolean,
@ -35,3 +36,8 @@ data class TimelineState(
val sessionState: SessionState,
val eventSink: (TimelineEvents) -> Unit
)
@Immutable
data class TimelineRoomInfo(
val isDirect: Boolean,
)

View file

@ -47,6 +47,7 @@ import kotlin.random.Random
fun aTimelineState(timelineItems: ImmutableList<TimelineItem> = persistentListOf()) = TimelineState(
timelineItems = timelineItems,
timelineRoomInfo = aTimelineRoomInfo(),
showReadReceipts = false,
paginationState = MatrixTimeline.PaginationState(
isBackPaginating = false,
@ -212,3 +213,9 @@ internal fun aGroupedEvents(id: Long = 0): TimelineItem.GroupedEvents {
aggregatedReadReceipts = events.flatMap { it.readReceiptState.receipts }.toImmutableList(),
)
}
internal fun aTimelineRoomInfo(
isDirect: Boolean = false,
) = TimelineRoomInfo(
isDirect = isDirect,
)

View file

@ -118,6 +118,7 @@ fun TimelineView(
) { timelineItem ->
TimelineItemRow(
timelineItem = timelineItem,
timelineRoomInfo = state.timelineRoomInfo,
showReadReceipts = state.showReadReceipts,
isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true
&& state.timelineItems.first().identifier() == timelineItem.identifier(),

View file

@ -17,17 +17,21 @@
package io.element.android.features.messages.impl.timeline.components
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItem
// For previews
@Composable
internal fun ATimelineItemEventRow(
event: TimelineItem.Event,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
showReadReceipts: Boolean = false,
isLastOutgoingMessage: Boolean = false,
isHighlighted: Boolean = false,
) = TimelineItemEventRow(
event = event,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,

View file

@ -35,17 +35,17 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.unit.dp
import io.element.android.compound.theme.ElementTheme
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
import io.element.android.libraries.core.extensions.to01
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
import io.element.android.libraries.designsystem.theme.components.Surface
import io.element.android.libraries.designsystem.theme.components.Text
import io.element.android.libraries.designsystem.theme.messageFromMeBackground
import io.element.android.libraries.designsystem.theme.messageFromOtherBackground
import io.element.android.compound.theme.ElementTheme
private val BUBBLE_RADIUS = 12.dp
private val BUBBLE_INCOMING_OFFSET = 16.dp
@ -91,10 +91,10 @@ fun MessageEventBubble(
}
fun Modifier.offsetForItem(): Modifier {
return if (state.isMine) {
this
} else {
offset(x = BUBBLE_INCOMING_OFFSET)
return when {
state.isMine -> this
state.timelineRoomInfo.isDirect -> this
else -> offset(x = BUBBLE_INCOMING_OFFSET)
}
}

View file

@ -60,6 +60,7 @@ import androidx.compose.ui.zIndex
import androidx.constraintlayout.compose.ConstrainScope
import androidx.constraintlayout.compose.ConstraintLayout
import io.element.android.compound.theme.ElementTheme
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.components.event.TimelineItemEventContentView
@ -101,6 +102,7 @@ import kotlin.math.roundToInt
@Composable
fun TimelineItemEventRow(
event: TimelineItem.Event,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
isHighlighted: Boolean,
@ -163,9 +165,8 @@ fun TimelineItemEventRow(
state = state.draggableState,
),
event = event,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
@ -175,7 +176,6 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onReadReceiptsClicked = { onReadReceiptClick(event) },
eventSink = eventSink,
)
}
@ -183,9 +183,8 @@ fun TimelineItemEventRow(
} else {
TimelineItemEventRowContent(
event = event,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
interactionSource = interactionSource,
onClick = onClick,
onLongClick = onLongClick,
@ -195,10 +194,20 @@ fun TimelineItemEventRow(
onReactionClicked = { emoji -> onReactionClick(emoji, event) },
onReactionLongClicked = { emoji -> onReactionLongClick(emoji, event) },
onMoreReactionsClicked = { onMoreReactionsClick(event) },
onReadReceiptsClicked = { onReadReceiptClick(event) },
eventSink = eventSink,
)
}
// Read receipts / Send state
TimelineItemReadReceiptView(
state = ReadReceiptViewState(
sendState = event.localSendState,
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
showReadReceipts = showReadReceipts,
onReadReceiptsClicked = { onReadReceiptClick(event) },
modifier = Modifier.padding(top = 4.dp),
)
}
}
@ -228,9 +237,8 @@ private fun SwipeSensitivity(
@Composable
private fun TimelineItemEventRowContent(
event: TimelineItem.Event,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
isHighlighted: Boolean,
timelineRoomInfo: TimelineRoomInfo,
interactionSource: MutableInteractionSource,
onClick: () -> Unit,
onLongClick: () -> Unit,
@ -238,7 +246,6 @@ private fun TimelineItemEventRowContent(
inReplyToClicked: () -> Unit,
onUserDataClicked: () -> Unit,
onReactionClicked: (emoji: String) -> Unit,
onReadReceiptsClicked: () -> Unit,
onReactionLongClicked: (emoji: String) -> Unit,
onMoreReactionsClicked: (event: TimelineItem.Event) -> Unit,
eventSink: (TimelineEvents) -> Unit,
@ -259,12 +266,11 @@ private fun TimelineItemEventRowContent(
sender,
message,
reactions,
readReceipts,
) = createRefs()
// Sender
val avatarStrokeSize = 3.dp
if (event.showSenderInformation) {
if (event.showSenderInformation && !timelineRoomInfo.isDirect) {
MessageSenderInformation(
event.safeSenderName,
event.senderAvatar,
@ -284,6 +290,7 @@ private fun TimelineItemEventRowContent(
groupPosition = event.groupPosition,
isMine = event.isMine,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
)
MessageEventBubble(
modifier = Modifier
@ -326,25 +333,6 @@ private fun TimelineItemEventRowContent(
.padding(start = if (event.isMine) 16.dp else 36.dp, end = 16.dp)
)
}
// Read receipts / Send state
TimelineItemReadReceiptView(
state = ReadReceiptViewState(
sendState = event.localSendState,
isLastOutgoingMessage = isLastOutgoingMessage,
receipts = event.readReceiptState.receipts,
),
showReadReceipts = showReadReceipts,
onReadReceiptsClicked = onReadReceiptsClicked,
modifier = Modifier
.constrainAs(readReceipts) {
if (event.reactionsState.reactions.isNotEmpty()) {
top.linkTo(reactions.bottom, margin = 4.dp)
} else {
top.linkTo(message.bottom, margin = 4.dp)
}
}
)
}
}
@ -378,6 +366,7 @@ private fun MessageSenderInformation(
Avatar(senderAvatar)
Spacer(modifier = Modifier.width(4.dp))
Text(
modifier = Modifier.clipToBounds(),
text = sender,
maxLines = 1,
overflow = TextOverflow.Ellipsis,

View file

@ -0,0 +1,62 @@
/*
* 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.foundation.layout.Column
import androidx.compose.runtime.Composable
import io.element.android.features.messages.impl.timeline.aTimelineItemEvent
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@PreviewsDayNight
@Composable
internal fun TimelineItemEventRowForDirectRoomPreview() = ElementPreview {
Column {
sequenceOf(false, true).forEach {
ATimelineItemEventRow(
event = aTimelineItemEvent(
isMine = it,
content = aTimelineItemTextContent().copy(
body = "A long text which will be displayed on several lines and" +
" hopefully can be manually adjusted to test different behaviors."
),
groupPosition = TimelineItemGroupPosition.First,
),
timelineRoomInfo = aTimelineRoomInfo(
isDirect = true,
),
)
ATimelineItemEventRow(
event = aTimelineItemEvent(
isMine = it,
content = aTimelineItemImageContent().copy(
aspectRatio = 5f
),
groupPosition = TimelineItemGroupPosition.Last,
),
timelineRoomInfo = aTimelineRoomInfo(
isDirect = true,
),
)
}
}
}

View file

@ -24,8 +24,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource
import io.element.android.features.messages.impl.R
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.aGroupedEvents
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.components.group.GroupHeaderView
import io.element.android.features.messages.impl.timeline.components.receipt.ReadReceiptViewState
import io.element.android.features.messages.impl.timeline.components.receipt.TimelineItemReadReceiptView
@ -40,6 +42,7 @@ import io.element.android.libraries.matrix.api.core.UserId
@Composable
fun TimelineItemGroupedEventsRow(
timelineItem: TimelineItem.GroupedEvents,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
highlightedItem: String?,
@ -66,6 +69,7 @@ fun TimelineItemGroupedEventsRow(
isExpanded = isExpanded.value,
onExpandGroupClick = ::onExpandGroupClick,
timelineItem = timelineItem,
timelineRoomInfo = timelineRoomInfo,
highlightedItem = highlightedItem,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
@ -89,6 +93,7 @@ private fun TimelineItemGroupedEventsRowContent(
isExpanded: Boolean,
onExpandGroupClick: () -> Unit,
timelineItem: TimelineItem.GroupedEvents,
timelineRoomInfo: TimelineRoomInfo,
highlightedItem: String?,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
@ -121,6 +126,7 @@ private fun TimelineItemGroupedEventsRowContent(
timelineItem.events.forEach { subGroupEvent ->
TimelineItemRow(
timelineItem = subGroupEvent,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
highlightedItem = highlightedItem,
@ -148,7 +154,8 @@ private fun TimelineItemGroupedEventsRowContent(
receipts = timelineItem.aggregatedReadReceipts,
),
showReadReceipts = true,
onReadReceiptsClicked = { /* No op for group event */ })
onReadReceiptsClicked = onExpandGroupClick
)
}
}
}
@ -160,6 +167,7 @@ internal fun TimelineItemGroupedEventsRowContentExpandedPreview() = ElementPrevi
isExpanded = true,
onExpandGroupClick = {},
timelineItem = aGroupedEvents(),
timelineRoomInfo = aTimelineRoomInfo(),
highlightedItem = null,
showReadReceipts = true,
isLastOutgoingMessage = false,
@ -184,6 +192,7 @@ internal fun TimelineItemGroupedEventsRowContentCollapsePreview() = ElementPrevi
isExpanded = false,
onExpandGroupClick = {},
timelineItem = aGroupedEvents(),
timelineRoomInfo = aTimelineRoomInfo(),
highlightedItem = null,
showReadReceipts = true,
isLastOutgoingMessage = false,

View file

@ -18,6 +18,7 @@ package io.element.android.features.messages.impl.timeline.components
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.TimelineEvents
import io.element.android.features.messages.impl.timeline.model.TimelineItem
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
@ -29,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.UserId
@Composable
internal fun TimelineItemRow(
timelineItem: TimelineItem,
timelineRoomInfo: TimelineRoomInfo,
showReadReceipts: Boolean,
isLastOutgoingMessage: Boolean,
highlightedItem: String?,
@ -71,6 +73,7 @@ internal fun TimelineItemRow(
} else {
TimelineItemEventRow(
event = timelineItem,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
isHighlighted = highlightedItem == timelineItem.identifier(),
@ -93,6 +96,7 @@ internal fun TimelineItemRow(
is TimelineItem.GroupedEvents -> {
TimelineItemGroupedEventsRow(
timelineItem = timelineItem,
timelineRoomInfo = timelineRoomInfo,
showReadReceipts = showReadReceipts,
isLastOutgoingMessage = isLastOutgoingMessage,
highlightedItem = highlightedItem,

View file

@ -16,6 +16,8 @@
package io.element.android.features.messages.impl.timeline.components.group
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@ -26,6 +28,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@ -76,9 +79,17 @@ fun GroupHeaderView(
color = MaterialTheme.colorScheme.secondary,
style = ElementTheme.typography.fontBodyMdRegular,
)
val rotation: Float by animateFloatAsState(
targetValue = if (isExpanded) 90f else 0f,
animationSpec = tween(
delayMillis = 0,
durationMillis = 300,
),
label = "chevron"
)
Icon(
modifier = Modifier.rotate(if (isExpanded) 180f else 0f),
imageVector = CompoundIcons.ChevronDown,
modifier = Modifier.rotate(rotation),
imageVector = CompoundIcons.ChevronRight,
contentDescription = null,
tint = MaterialTheme.colorScheme.secondary
)

View file

@ -16,10 +16,12 @@
package io.element.android.features.messages.impl.timeline.model.bubble
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
data class BubbleState(
val groupPosition: TimelineItemGroupPosition,
val isMine: Boolean,
val isHighlighted: Boolean,
val timelineRoomInfo: TimelineRoomInfo,
)

View file

@ -17,6 +17,8 @@
package io.element.android.features.messages.impl.timeline.model.bubble
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import io.element.android.features.messages.impl.timeline.TimelineRoomInfo
import io.element.android.features.messages.impl.timeline.aTimelineRoomInfo
import io.element.android.features.messages.impl.timeline.model.TimelineItemGroupPosition
open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
@ -29,7 +31,11 @@ open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
).map { groupPosition ->
sequenceOf(false, true).map { isMine ->
sequenceOf(false, true).map { isHighlighted ->
BubbleState(groupPosition, isMine = isMine, isHighlighted = isHighlighted)
aBubbleState(
groupPosition = groupPosition,
isMine = isMine,
isHighlighted = isHighlighted,
)
}
}
.flatten()
@ -37,8 +43,14 @@ open class BubbleStateProvider : PreviewParameterProvider<BubbleState> {
.flatten()
}
fun aBubbleState() = BubbleState(
groupPosition = TimelineItemGroupPosition.First,
isMine = false,
isHighlighted = false,
internal fun aBubbleState(
groupPosition: TimelineItemGroupPosition = TimelineItemGroupPosition.First,
isMine: Boolean = false,
isHighlighted: Boolean = false,
timelineRoomInfo: TimelineRoomInfo = aTimelineRoomInfo(),
) = BubbleState(
groupPosition = groupPosition,
isMine = isMine,
isHighlighted = isHighlighted,
timelineRoomInfo = timelineRoomInfo,
)

View file

@ -24,6 +24,7 @@ import io.element.android.libraries.di.CacheDirectory
import io.element.android.libraries.di.RoomScope
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.mxc.MxcTools
import java.io.File
/**
@ -66,6 +67,7 @@ interface VoiceMessageMediaRepo {
class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
@CacheDirectory private val cacheDir: File,
mxcTools: MxcTools,
private val matrixMediaLoader: MatrixMediaLoader,
@Assisted private val mediaSource: MediaSource,
@Assisted("mimeType") private val mimeType: String?,
@ -101,7 +103,7 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
}
}
private val cachedFile: File? = mxcUri2FilePath(mediaSource.url)?.let {
private val cachedFile: File? = mxcTools.mxcUri2FilePath(mediaSource.url)?.let {
File("${cacheDir.path}/$CACHE_VOICE_SUBDIR/$it")
}
}
@ -110,24 +112,3 @@ class DefaultVoiceMessageMediaRepo @AssistedInject constructor(
* Subdirectory of the application's cache directory where voice messages are stored.
*/
private const val CACHE_VOICE_SUBDIR = "temp/voice"
/**
* Regex to match a Matrix Content (mxc://) URI.
*
* See: https://spec.matrix.org/v1.8/client-server-api/#matrix-content-mxc-uris
*/
private val mxcRegex = Regex("""^mxc:\/\/([^\/]+)\/([^\/]+)$""")
/**
* Sanitizes an mxcUri to be used as a relative file path.
*
* @param mxcUri the Matrix Content (mxc://) URI of the voice message.
* @return the relative file path as "<server-name>/<media-id>" or null if the mxcUri is invalid.
*/
private fun mxcUri2FilePath(mxcUri: String): String? = mxcRegex.matchEntire(mxcUri)?.let { match ->
buildString {
append(match.groupValues[1])
append("/")
append(match.groupValues[2])
}
}

View file

@ -17,6 +17,7 @@
<string name="room_timeline_beginning_of_room">"Dies ist der Anfang von %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Dies ist der Anfang dieses Gesprächs."</string>
<string name="room_timeline_read_marker_title">"Neu"</string>
<string name="screen_room_mentions_at_room_subtitle">"Den ganzen Raum benachrichtigen"</string>
<string name="screen_report_content_block_user_hint">"Prüfe, ob du alle aktuellen und zukünftigen Nachrichten dieses Benutzers ausblenden möchtest"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Foto machen"</string>
@ -27,6 +28,7 @@
<string name="screen_room_attachment_source_poll">"Umfrage"</string>
<string name="screen_room_attachment_text_formatting">"Textformatierung"</string>
<string name="screen_room_encrypted_history_banner">"Der Nachrichtenverlauf ist derzeit in diesem Raum nicht verfügbar"</string>
<string name="screen_room_encrypted_history_banner_unverified">"Der Nachrichtenverlauf ist in diesem Raum nicht verfügbar. Verifiziere dieses Gerät, um deinen Nachrichtenverlauf zu sehen."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Benutzerdetails konnten nicht abgerufen werden"</string>
<string name="screen_room_invite_again_alert_message">"Möchtest du sie wieder einladen?"</string>
<string name="screen_room_invite_again_alert_title">"Du bist allein in diesem Chat"</string>
@ -42,6 +44,7 @@
<string name="screen_room_notification_settings_error_loading_settings">"Beim Laden der Benachrichtigungseinstellungen ist ein Fehler aufgetreten."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Fehler beim Wiederherstellen des Standardmodus. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Fehler beim Einstellen des Modus. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"Dein Homeserver unterstützt diese Option in verschlüsselten Räumen nicht. Du wirst in diesem Raum nicht benachrichtigt."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Alle Nachrichten"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Benachrichtige mich in diesem Raum bei"</string>
<string name="screen_room_reactions_show_less">"Weniger anzeigen"</string>
@ -50,6 +53,8 @@
<string name="screen_room_retry_send_menu_title">"Deine Nachricht konnte nicht gesendet werden"</string>
<string name="screen_room_timeline_add_reaction">"Emoji hinzufügen"</string>
<string name="screen_room_timeline_less_reactions">"Weniger anzeigen"</string>
<string name="screen_room_voice_message_tooltip">"Zum Aufnehmen gedrückt halten"</string>
<string name="screen_room_mentions_at_room_title">"Alle"</string>
<string name="screen_report_content_block_user">"Benutzer sperren"</string>
<string name="screen_room_error_failed_processing_media">"Fehler beim Verarbeiten des hochgeladenen Mediums. Bitte versuche es erneut."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Nur Erwähnungen und Schlüsselwörter"</string>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Tevékenységek"</string>
<string name="emoji_picker_category_flags">"Zászlók"</string>
<string name="emoji_picker_category_foods">"Étel és ital"</string>
<string name="emoji_picker_category_nature">"Állatok és természet"</string>
<string name="emoji_picker_category_objects">"Tárgyak"</string>
<string name="emoji_picker_category_people">"Mosolyok és emberek"</string>
<string name="emoji_picker_category_places">"Utazás és helyek"</string>
<string name="emoji_picker_category_symbols">"Szimbólumok"</string>
<string name="report_content_explanation">"Ez az üzenet jelentve lesz a Matrix-kiszolgáló rendszergazdájának. Nem fogja tudni elolvasni a titkosított üzeneteket."</string>
<string name="report_content_hint">"A tartalom jelentésének oka"</string>
<string name="room_timeline_beginning_of_room">"Ez a(z) %1$s kezdete."</string>
<string name="room_timeline_beginning_of_room_no_name">"Ez a beszélgetés kezdete."</string>
<string name="room_timeline_read_marker_title">"Új"</string>
<string name="screen_room_mentions_at_room_subtitle">"Teljes szoba értesítése"</string>
<string name="screen_report_content_block_user_hint">"Jelölje be, ha el akarja rejteni az összes jelenlegi és jövőbeli üzenetet ettől a felhasználótól"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Fénykép készítése"</string>
<string name="screen_room_attachment_source_camera_video">"Videó rögzítése"</string>
<string name="screen_room_attachment_source_files">"Melléklet"</string>
<string name="screen_room_attachment_source_gallery">"Fénykép- és videótár"</string>
<string name="screen_room_attachment_source_location">"Hely"</string>
<string name="screen_room_attachment_source_poll">"Szavazás"</string>
<string name="screen_room_attachment_text_formatting">"Szövegformázás"</string>
<string name="screen_room_encrypted_history_banner">"Az üzenetelőzmények jelenleg nem érhetők el."</string>
<string name="screen_room_encrypted_history_banner_unverified">"Az üzenetelőzmények nem érhetők el ebben a szobában. Ellenőrizze ezt az eszközt, hogy lássa az előzményeket."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Nem sikerült letölteni a felhasználói adatokat"</string>
<string name="screen_room_invite_again_alert_message">"Visszahívja?"</string>
<string name="screen_room_invite_again_alert_title">"Egyedül van ebben a csevegésben"</string>
<string name="screen_room_message_copied">"Üzenet másolva"</string>
<string name="screen_room_no_permission_to_post">"Nincs jogosultsága arra, hogy bejegyzést tegyen közzé ebben a szobában"</string>
<string name="screen_room_notification_settings_allow_custom">"Egyéni beállítás engedélyezése"</string>
<string name="screen_room_notification_settings_allow_custom_footnote">"Ennek bekapcsolása felülírja az alapértelmezett beállítást"</string>
<string name="screen_room_notification_settings_custom_settings_title">"Értesítések kérése ebben a csevegésben ezekről:"</string>
<string name="screen_room_notification_settings_default_setting_footnote">"Megváltoztathatja a %1$s."</string>
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"globális beállításokban"</string>
<string name="screen_room_notification_settings_default_setting_title">"Alapértelmezett beállítás"</string>
<string name="screen_room_notification_settings_edit_remove_setting">"Egyéni beállítás eltávolítása"</string>
<string name="screen_room_notification_settings_error_loading_settings">"Hiba történt az értesítési beállítások betöltésekor."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Nem sikerült visszaállítani az alapértelmezett módot, próbálja újra."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Nem sikerült a mód beállítása, próbálja újra."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"A Matrix-kiszolgálója nem támogatja ezt a beállítást a titkosított szobákban, egyes szobákban nem fog értesítéseket kapni."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Összes üzenet"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Ebben a szobában, értesítés ezekről:"</string>
<string name="screen_room_reactions_show_less">"Kevesebb megjelenítése"</string>
<string name="screen_room_reactions_show_more">"Több megjelenítése"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Újraküldés"</string>
<string name="screen_room_retry_send_menu_title">"Az üzenet elküldése sikertelen"</string>
<string name="screen_room_timeline_add_reaction">"Emodzsi hozzáadása"</string>
<string name="screen_room_timeline_less_reactions">"Kevesebb megjelenítése"</string>
<string name="screen_room_voice_message_tooltip">"Tartsa a rögzítéshez"</string>
<string name="screen_room_mentions_at_room_title">"Mindenki"</string>
<string name="screen_report_content_block_user">"Felhasználó letiltása"</string>
<string name="screen_room_error_failed_processing_media">"Nem sikerült feldolgozni a feltöltendő médiát, próbálja újra."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Csak említések és kulcsszavak"</string>
</resources>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="emoji_picker_category_activity">"Aktivitas"</string>
<string name="emoji_picker_category_flags">"Bendera"</string>
<string name="emoji_picker_category_foods">"Makanan &amp; Minuman"</string>
<string name="emoji_picker_category_nature">"Hewan &amp; Alam"</string>
<string name="emoji_picker_category_objects">"Objek"</string>
<string name="emoji_picker_category_people">"Senyuman &amp; Orang"</string>
<string name="emoji_picker_category_places">"Wisata &amp; Tempat"</string>
<string name="emoji_picker_category_symbols">"Simbol"</string>
<plurals name="room_timeline_state_changes">
<item quantity="other">"%1$d perubahan ruangan"</item>
</plurals>
<string name="report_content_explanation">"Pesan ini akan dilaporkan ke administrator homeserver Anda. Mereka tidak akan dapat membaca pesan terenkripsi apa pun."</string>
<string name="report_content_hint">"Alasan melaporkan konten ini"</string>
<string name="room_timeline_beginning_of_room">"Ini adalah awal dari %1$s."</string>
<string name="room_timeline_beginning_of_room_no_name">"Ini adalah awal dari percakapan ini."</string>
<string name="room_timeline_read_marker_title">"Baru"</string>
<string name="screen_room_mentions_at_room_subtitle">"Beri tahu seluruh ruangan"</string>
<string name="screen_report_content_block_user_hint">"Centang jika Anda ingin menyembunyikan semua pesan saat ini dan yang akan datang dari pengguna ini"</string>
<string name="screen_room_attachment_source_camera">"Kamera"</string>
<string name="screen_room_attachment_source_camera_photo">"Ambil foto"</string>
<string name="screen_room_attachment_source_camera_video">"Rekam video"</string>
<string name="screen_room_attachment_source_files">"Lampiran"</string>
<string name="screen_room_attachment_source_gallery">"Pustaka Foto &amp; Video"</string>
<string name="screen_room_attachment_source_location">"Lokasi"</string>
<string name="screen_room_attachment_source_poll">"Pemungutan suara"</string>
<string name="screen_room_attachment_text_formatting">"Pemformatan Teks"</string>
<string name="screen_room_encrypted_history_banner">"Riwayat pesan saat ini tidak tersedia di ruangan ini"</string>
<string name="screen_room_encrypted_history_banner_unverified">"Riwayat pesan tidak tersedia di ruangan ini. Verifikasi perangkat ini untuk melihat riwayat pesan."</string>
<string name="screen_room_error_failed_retrieving_user_details">"Tidak dapat mengambil detail pengguna"</string>
<string name="screen_room_invite_again_alert_message">"Apakah Anda ingin mengundang mereka kembali?"</string>
<string name="screen_room_invite_again_alert_title">"Anda sendirian di obrolan ini"</string>
<string name="screen_room_message_copied">"Pesan disalin"</string>
<string name="screen_room_no_permission_to_post">"Anda tidak memiliki izin untuk mengirim di ruangan ini"</string>
<string name="screen_room_notification_settings_allow_custom">"Izinkan pengaturan khusus"</string>
<string name="screen_room_notification_settings_allow_custom_footnote">"Mengaktifkan ini akan mengganti pengaturan bawaan Anda"</string>
<string name="screen_room_notification_settings_custom_settings_title">"Beri tahu saya di obrolan ini tentang"</string>
<string name="screen_room_notification_settings_default_setting_footnote">"Anda dapat mengubahnya di %1$s Anda."</string>
<string name="screen_room_notification_settings_default_setting_footnote_content_link">"pengaturan global"</string>
<string name="screen_room_notification_settings_default_setting_title">"Pengaturan bawaan"</string>
<string name="screen_room_notification_settings_edit_remove_setting">"Hapus pengaturan khusus"</string>
<string name="screen_room_notification_settings_error_loading_settings">"Terjadi kesalahan saat memuat pengaturan pemberitahuan."</string>
<string name="screen_room_notification_settings_error_restoring_default">"Gagal memulihkan mode bawaan, silakan coba lagi."</string>
<string name="screen_room_notification_settings_error_setting_mode">"Gagal mengatur mode, silakan coba lagi."</string>
<string name="screen_room_notification_settings_mentions_only_disclaimer">"Homeserver Anda tidak mendukung opsi ini dalam ruangan terenkripsi, Anda tidak akan diberi tahu dalam ruangan ini."</string>
<string name="screen_room_notification_settings_mode_all_messages">"Semua pesan"</string>
<string name="screen_room_notification_settings_room_custom_settings_title">"Di ruangan ini, beri tahu saya tentang"</string>
<string name="screen_room_reactions_show_less">"Tampilkan lebih sedikit"</string>
<string name="screen_room_reactions_show_more">"Tampilkan lebih banyak"</string>
<string name="screen_room_retry_send_menu_send_again_action">"Kirim ulang"</string>
<string name="screen_room_retry_send_menu_title">"Pesan Anda gagal dikirim"</string>
<string name="screen_room_timeline_add_reaction">"Tambahkan emoji"</string>
<string name="screen_room_timeline_less_reactions">"Tampilkan lebih sedikit"</string>
<string name="screen_room_voice_message_tooltip">"Tahan untuk merekam"</string>
<string name="screen_room_mentions_at_room_title">"Semua orang"</string>
<string name="screen_report_content_block_user">"Blokir pengguna"</string>
<string name="screen_room_error_failed_processing_media">"Gagal memproses media untuk diunggah, silakan coba lagi."</string>
<string name="screen_room_notification_settings_mode_mentions_and_keywords">"Sebutan dan Kata Kunci saja"</string>
</resources>

View file

@ -379,9 +379,9 @@ class MessageComposerPresenterTest {
}.test {
skipItems(1)
val initialState = awaitItem()
assertThat(initialState.showAttachmentSourcePicker).isEqualTo(false)
assertThat(initialState.showAttachmentSourcePicker).isFalse()
initialState.eventSink(MessageComposerEvents.AddAttachment)
assertThat(awaitItem().showAttachmentSourcePicker).isEqualTo(true)
assertThat(awaitItem().showAttachmentSourcePicker).isTrue()
}
}

View file

@ -53,7 +53,7 @@ class ReactionSummaryPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(null)
assertThat(initialState.target).isNull()
initialState.eventSink(summaryEvent)
assertThat(awaitItem().target).isNotNull()
@ -69,7 +69,7 @@ class ReactionSummaryPresenterTests {
presenter.present()
}.test {
val initialState = awaitItem()
assertThat(initialState.target).isEqualTo(null)
assertThat(initialState.target).isNull()
initialState.eventSink(summaryEvent)
val reactions = awaitItem().target?.reactions

View file

@ -26,7 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.AudioInfo
@ -59,7 +59,7 @@ class InReplyToMetadataKtTest {
anInReplyToDetails(eventContent = aMessageContent()).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(InReplyToMetadata.Text("textContent"))
assertThat(it).isEqualTo(InReplyToMetadata.Text("textContent"))
}
}
}
@ -78,7 +78,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -115,7 +115,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -148,7 +148,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = aMediaSource(),
@ -180,7 +180,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
textContent = "body",
@ -209,7 +209,7 @@ class InReplyToMetadataKtTest {
}
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -240,7 +240,7 @@ class InReplyToMetadataKtTest {
}
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -262,7 +262,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(
assertThat(it).isEqualTo(
InReplyToMetadata.Thumbnail(
attachmentThumbnailInfo = AttachmentThumbnailInfo(
thumbnailSource = null,
@ -284,7 +284,7 @@ class InReplyToMetadataKtTest {
).metadata()
}.test {
awaitItem().let {
Truth.assertThat(it).isEqualTo(null)
assertThat(it).isNull()
}
}
}

View file

@ -16,10 +16,11 @@
package io.element.android.features.messages.impl.voicemessages.timeline
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.mxc.MxcTools
import io.element.android.libraries.matrix.test.media.FakeMediaLoader
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@ -43,10 +44,10 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isSuccess).isTrue()
assertThat(result.isSuccess).isTrue()
result.getOrThrow().let { file ->
Truth.assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
Truth.assertThat(file.exists()).isTrue()
assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
assertThat(file.exists()).isTrue()
}
}
}
@ -62,9 +63,9 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()!!.let { exception ->
Truth.assertThat(exception).isInstanceOf(RuntimeException::class.java)
assertThat(exception).isInstanceOf(RuntimeException::class.java)
}
}
}
@ -87,9 +88,9 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()?.let { exception ->
Truth.assertThat(exception).apply {
assertThat(exception).apply {
isInstanceOf(IllegalStateException::class.java)
hasMessageThat().isEqualTo("Failed to move file to cache.")
}
@ -109,10 +110,10 @@ class DefaultVoiceMessageMediaRepoTest {
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isSuccess).isTrue()
assertThat(result.isSuccess).isTrue()
result.getOrThrow().let { file ->
Truth.assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
Truth.assertThat(file.exists()).isTrue()
assertThat(file.path).isEqualTo(temporaryFolder.cachedFilePath)
assertThat(file.exists()).isTrue()
}
}
}
@ -124,10 +125,10 @@ class DefaultVoiceMessageMediaRepoTest {
mxcUri = INVALID_MXC_URI,
)
repo.getMediaFile().let { result ->
Truth.assertThat(result.isFailure).isTrue()
assertThat(result.isFailure).isTrue()
result.exceptionOrNull()!!.let { exception ->
Truth.assertThat(exception).isInstanceOf(RuntimeException::class.java)
Truth.assertThat(exception).hasMessageThat().isEqualTo("Invalid mxcUri.")
assertThat(exception).isInstanceOf(RuntimeException::class.java)
assertThat(exception).hasMessageThat().isEqualTo("Invalid mxcUri.")
}
}
}
@ -139,6 +140,7 @@ private fun createDefaultVoiceMessageMediaRepo(
mxcUri: String = MXC_URI,
) = DefaultVoiceMessageMediaRepo(
cacheDir = temporaryFolder.root,
mxcTools = MxcTools(),
matrixMediaLoader = matrixMediaLoader,
mediaSource = MediaSource(
url = mxcUri,

View file

@ -18,7 +18,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline
import app.cash.turbine.TurbineTestContext
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.media.MediaSource
@ -42,7 +42,7 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
}
}
@ -56,7 +56,7 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isFailure).isTrue()
assertThat(player.prepare().isFailure).isTrue()
}
}
@ -67,7 +67,7 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isFailure).isTrue()
assertThat(player.prepare().isFailure).isTrue()
}
}
@ -76,12 +76,12 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.isPlaying).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
}
}
}
@ -96,15 +96,15 @@ class DefaultVoiceMessagePlayerTest {
)
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState(fakeTotalDurationMs = 1000)
player.play()
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
}
@ -121,72 +121,72 @@ class DefaultVoiceMessagePlayerTest {
// Play player1 until the end.
player1.state.test {
matchInitialState()
Truth.assertThat(player1.prepare().isSuccess).isTrue()
assertThat(player1.prepare().isSuccess).isTrue()
matchReadyState(1_000L)
player1.play()
awaitItem().let { // it plays until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
// Play player2 until the end.
player2.state.test {
matchInitialState()
Truth.assertThat(player2.prepare().isSuccess).isTrue()
assertThat(player2.prepare().isSuccess).isTrue()
awaitItem().let { // Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
awaitItem().let {// Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
matchReadyState(1_000L)
player2.play()
awaitItem().let { // it plays until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
// Play player1 again.
player1.state.test {
awaitItem().let {// Last previous state/
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
Truth.assertThat(player1.prepare().isSuccess).isTrue()
assertThat(player1.prepare().isSuccess).isTrue()
awaitItem().let {// Additional spurious state due to MediaPlayer owner change.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
matchReadyState(1_000L)
player1.play()
awaitItem().let { // it played again until the end.
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
Truth.assertThat(it.duration).isEqualTo(1000)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isTrue()
assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.duration).isEqualTo(1000)
}
}
}
@ -196,14 +196,14 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
skipItems(1) // skip play state
player.pause()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(1000)
assertThat(it.isPlaying).isFalse()
assertThat(it.currentPosition).isEqualTo(1000)
}
}
}
@ -213,7 +213,7 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.play()
skipItems(1) // skip play state
@ -221,8 +221,8 @@ class DefaultVoiceMessagePlayerTest {
skipItems(1) // skip pause state
player.play()
awaitItem().let {
Truth.assertThat(it.isPlaying).isEqualTo(true)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.isPlaying).isTrue()
assertThat(it.currentPosition).isEqualTo(2000)
}
}
}
@ -234,19 +234,19 @@ class DefaultVoiceMessagePlayerTest {
matchInitialState()
player.seekTo(2000)
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isNull()
}
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
}
}
}
@ -256,15 +256,15 @@ class DefaultVoiceMessagePlayerTest {
val player = createDefaultVoiceMessagePlayer()
player.state.test {
matchInitialState()
Truth.assertThat(player.prepare().isSuccess).isTrue()
assertThat(player.prepare().isSuccess).isTrue()
matchReadyState()
player.seekTo(2000)
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(2000)
Truth.assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(2000)
assertThat(it.duration).isEqualTo(FAKE_TOTAL_DURATION_MS)
}
}
}
@ -296,11 +296,11 @@ private const val MXC_URI = "mxc://matrix.org/1234567890abcdefg"
private suspend fun TurbineTestContext<VoiceMessagePlayer.State>.matchInitialState() {
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(false)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(null)
assertThat(it.isReady).isFalse()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isNull()
}
}
@ -308,10 +308,10 @@ private suspend fun TurbineTestContext<VoiceMessagePlayer.State>.matchReadyState
fakeTotalDurationMs: Long = FAKE_TOTAL_DURATION_MS,
) {
awaitItem().let {
Truth.assertThat(it.isReady).isEqualTo(true)
Truth.assertThat(it.isPlaying).isEqualTo(false)
Truth.assertThat(it.isEnded).isEqualTo(false)
Truth.assertThat(it.currentPosition).isEqualTo(0)
Truth.assertThat(it.duration).isEqualTo(fakeTotalDurationMs)
assertThat(it.isReady).isTrue()
assertThat(it.isPlaying).isFalse()
assertThat(it.isEnded).isFalse()
assertThat(it.currentPosition).isEqualTo(0)
assertThat(it.duration).isEqualTo(fakeTotalDurationMs)
}
}

View file

@ -16,7 +16,7 @@
package io.element.android.features.messages.impl.voicemessages.timeline
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.core.mimetype.MimeTypes
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
@ -44,13 +44,13 @@ class RedactedVoiceMessageManagerTest {
}
val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer)
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID_2))
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
}
@Test
@ -61,13 +61,13 @@ class RedactedVoiceMessageManagerTest {
}
val manager = aDefaultRedactedVoiceMessageManager(mediaPlayer = mediaPlayer)
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isTrue()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isTrue()
manager.onEachMatrixTimelineItem(aRedactedMatrixTimeline(AN_EVENT_ID))
Truth.assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
Truth.assertThat(mediaPlayer.state.value.isPlaying).isFalse()
assertThat(mediaPlayer.state.value.mediaId).isEqualTo(AN_EVENT_ID.value)
assertThat(mediaPlayer.state.value.isPlaying).isFalse()
}
}

View file

@ -19,7 +19,7 @@ package io.element.android.features.messages.impl.voicemessages.timeline
import app.cash.molecule.RecompositionMode
import app.cash.molecule.moleculeFlow
import app.cash.turbine.test
import com.google.common.truth.Truth
import com.google.common.truth.Truth.assertThat
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVoiceContent
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
import io.element.android.features.messages.impl.voicemessages.VoiceMessageException
@ -39,9 +39,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
awaitItem().let {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("1:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("1:01")
}
}
}
@ -56,27 +56,27 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:00")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:00")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
}
}
@ -94,25 +94,25 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Downloading)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Retry)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Retry)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
analyticsService.trackedErrors.first().also {
Truth.assertThat(it).apply {
assertThat(it).apply {
isInstanceOf(VoiceMessageException.PlayMessageError::class.java)
hasMessageThat().isEqualTo("Error while trying to play voice message")
}
@ -130,25 +130,25 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:02")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:02")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
skipItems(2) // skip downloading states
val playingState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
playingState.eventSink(VoiceMessageEvents.PlayPause)
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:01")
}
}
}
@ -162,9 +162,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Disabled)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("1:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Disabled)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("1:01")
}
}
}
@ -179,17 +179,17 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:10")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:10")
}
initialState.eventSink(VoiceMessageEvents.Seek(0.5f))
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:05")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:05")
}
}
}
@ -203,9 +203,9 @@ class VoiceMessagePresenterTest {
presenter.present()
}.test {
val initialState = awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
Truth.assertThat(it.progress).isEqualTo(0f)
Truth.assertThat(it.time).isEqualTo("0:10")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Play)
assertThat(it.progress).isEqualTo(0f)
assertThat(it.time).isEqualTo("0:10")
}
initialState.eventSink(VoiceMessageEvents.PlayPause)
@ -213,17 +213,17 @@ class VoiceMessagePresenterTest {
skipItems(2) // skip downloading states
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.1f)
Truth.assertThat(it.time).isEqualTo("0:01")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.1f)
assertThat(it.time).isEqualTo("0:01")
}
initialState.eventSink(VoiceMessageEvents.Seek(0.5f))
awaitItem().also {
Truth.assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
Truth.assertThat(it.progress).isEqualTo(0.5f)
Truth.assertThat(it.time).isEqualTo("0:05")
assertThat(it.button).isEqualTo(VoiceMessageState.Button.Pause)
assertThat(it.progress).isEqualTo(0.5f)
assertThat(it.time).isEqualTo("0:05")
}
}
}