mirror of
https://github.com/element-hq/element-android
synced 2024-12-18 15:24:33 +03:00
Merge pull request #7898 from vector-im/bugfix/fre/unexpected_live_vb_room_list
Fix unexpected live voice broadcast in the room list
This commit is contained in:
commit
b1d2581bf3
8 changed files with 471 additions and 34 deletions
1
changelog.d/7832.bugfix
Normal file
1
changelog.d/7832.bugfix
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[Voice Broadcast] Fix unexpected "live broadcast" in the room list
|
|
@ -22,41 +22,33 @@ import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
|
import im.vector.app.features.home.room.list.usecase.GetLatestPreviewableEventUseCase
|
||||||
import im.vector.app.features.home.room.typing.TypingHelper
|
import im.vector.app.features.home.room.typing.TypingHelper
|
||||||
import im.vector.app.features.voicebroadcast.isLive
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
|
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
|
||||||
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryItemFactory @Inject constructor(
|
class RoomSummaryItemFactory @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
|
||||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val errorFormatter: ErrorFormatter,
|
private val errorFormatter: ErrorFormatter,
|
||||||
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
private val getLatestPreviewableEventUseCase: GetLatestPreviewableEventUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
@ -142,7 +134,7 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
||||||
var latestFormattedEvent: CharSequence = ""
|
var latestFormattedEvent: CharSequence = ""
|
||||||
var latestEventTime = ""
|
var latestEventTime = ""
|
||||||
val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
|
val latestEvent = getLatestPreviewableEventUseCase.execute(roomSummary.roomId)
|
||||||
if (latestEvent != null) {
|
if (latestEvent != null) {
|
||||||
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
||||||
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
||||||
|
@ -150,7 +142,8 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
|
|
||||||
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
||||||
// Skip typing while there is a live voice broadcast
|
// Skip typing while there is a live voice broadcast
|
||||||
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty()
|
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }
|
||||||
|
.orEmpty()
|
||||||
|
|
||||||
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
||||||
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
||||||
|
@ -240,14 +233,4 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? {
|
|
||||||
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent
|
|
||||||
val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull()
|
|
||||||
?.root?.eventId?.let { room.getTimelineEvent(it) }
|
|
||||||
return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
|
|
||||||
?: liveVoiceBroadcastTimelineEvent
|
|
||||||
?: latestPreviewableEvent
|
|
||||||
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.home.room.list.usecase
|
||||||
|
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
|
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetLatestPreviewableEventUseCase @Inject constructor(
|
||||||
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(roomId: String): TimelineEvent? {
|
||||||
|
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return null
|
||||||
|
val roomSummary = room.roomSummary() ?: return null
|
||||||
|
return getCallEvent(roomSummary)
|
||||||
|
?: getLiveVoiceBroadcastEvent(room)
|
||||||
|
?: getDefaultLatestEvent(room, roomSummary)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? {
|
||||||
|
return roomSummary.latestPreviewableEvent
|
||||||
|
?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getLiveVoiceBroadcastEvent(room: Room): TimelineEvent? {
|
||||||
|
return getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
|
||||||
|
.lastOrNull()
|
||||||
|
?.voiceBroadcastId
|
||||||
|
?.let { room.getTimelineEvent(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getDefaultLatestEvent(room: Room, roomSummary: RoomSummary): TimelineEvent? {
|
||||||
|
val latestPreviewableEvent = roomSummary.latestPreviewableEvent
|
||||||
|
|
||||||
|
// If the default latest event is a live voice broadcast (paused or resumed), rely to the started event
|
||||||
|
val liveVoiceBroadcastEventId = latestPreviewableEvent?.root?.asVoiceBroadcastEvent()?.takeIf { it.isLive }?.voiceBroadcastId
|
||||||
|
if (liveVoiceBroadcastEventId != null) {
|
||||||
|
return room.getTimelineEvent(liveVoiceBroadcastEventId)
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestPreviewableEvent
|
||||||
|
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,14 +19,20 @@ package im.vector.app.features.voicebroadcast.usecase
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
import im.vector.app.features.voicebroadcast.isLive
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the list of live (not ended) voice broadcast events in the given room.
|
||||||
|
*/
|
||||||
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
|
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
||||||
|
@ -37,7 +43,8 @@ class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
||||||
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||||
QueryStringValue.IsNotEmpty
|
QueryStringValue.IsNotEmpty
|
||||||
)
|
)
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { stateEvent -> stateEvent.asVoiceBroadcastEvent()?.voiceBroadcastId }
|
||||||
|
.mapNotNull { voiceBroadcastId -> getVoiceBroadcastStateEventUseCase.execute(VoiceBroadcast(voiceBroadcastId, roomId)) }
|
||||||
.filter { it.isLive }
|
.filter { it.isLive }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.flow.transformWhile
|
import kotlinx.coroutines.flow.transformWhile
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.Room
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
import org.matrix.android.sdk.api.util.Optional
|
import org.matrix.android.sdk.api.util.Optional
|
||||||
|
@ -44,6 +43,7 @@ import javax.inject.Inject
|
||||||
|
|
||||||
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
|
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
||||||
|
@ -93,7 +93,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||||
* Get a flow of the most recent related event.
|
* Get a flow of the most recent related event.
|
||||||
*/
|
*/
|
||||||
private fun getMostRecentRelatedEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
private fun getMostRecentRelatedEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
|
||||||
val mostRecentEvent = getMostRecentRelatedEvent(room, voiceBroadcast).toOptional()
|
val mostRecentEvent = getVoiceBroadcastStateEventUseCase.execute(voiceBroadcast).toOptional()
|
||||||
return if (mostRecentEvent.hasValue()) {
|
return if (mostRecentEvent.hasValue()) {
|
||||||
val stateKey = mostRecentEvent.get().root.stateKey.orEmpty()
|
val stateKey = mostRecentEvent.get().root.stateKey.orEmpty()
|
||||||
// observe incoming voice broadcast state events
|
// observe incoming voice broadcast state events
|
||||||
|
@ -141,15 +141,6 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the most recent event related to the given voice broadcast.
|
|
||||||
*/
|
|
||||||
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
|
||||||
return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
|
|
||||||
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } }
|
|
||||||
.maxByOrNull { it.root.originServerTs ?: 0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a flow of the given voice broadcast event changes.
|
* Get a flow of the given voice broadcast event changes.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2022 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 im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||||
|
import org.matrix.android.sdk.api.extensions.orTrue
|
||||||
|
import org.matrix.android.sdk.api.session.Session
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetVoiceBroadcastStateEventUseCase @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
||||||
|
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}")
|
||||||
|
return getMostRecentRelatedEvent(room, voiceBroadcast)
|
||||||
|
.also { event ->
|
||||||
|
Timber.d(
|
||||||
|
"## VoiceBroadcast | " +
|
||||||
|
"voiceBroadcastId=${event?.voiceBroadcastId}, " +
|
||||||
|
"state=${event?.content?.voiceBroadcastState}"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the most recent event related to the given voice broadcast.
|
||||||
|
*/
|
||||||
|
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
|
||||||
|
val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId)
|
||||||
|
return if (startedEvent?.root?.isRedacted().orTrue()) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
|
||||||
|
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent() }
|
||||||
|
.filterNot { it.root.isRedacted() }
|
||||||
|
.maxByOrNull { it.root.originServerTs ?: 0 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,196 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.home.room.list.usecase
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants.VOICE_BROADCAST_CHUNK_KEY
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
|
import im.vector.app.test.fakes.FakeRoom
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "a-room-id"
|
||||||
|
|
||||||
|
internal class GetLatestPreviewableEventUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoom = FakeRoom()
|
||||||
|
private val fakeSessionHolder = FakeActiveSessionHolder()
|
||||||
|
private val fakeRoomSummary = mockk<RoomSummary>()
|
||||||
|
private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk<GetRoomLiveVoiceBroadcastsUseCase>()
|
||||||
|
|
||||||
|
private val getLatestPreviewableEventUseCase = GetLatestPreviewableEventUseCase(
|
||||||
|
fakeSessionHolder.instance,
|
||||||
|
fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
every { fakeSessionHolder.instance.getSafeActiveSession()?.getRoom(A_ROOM_ID) } returns fakeRoom
|
||||||
|
every { fakeRoom.roomSummary() } returns fakeRoomSummary
|
||||||
|
every { fakeRoom.roomId } returns A_ROOM_ID
|
||||||
|
every { fakeRoom.timelineService().getTimelineEvent(any()) } answers {
|
||||||
|
mockk(relaxed = true) {
|
||||||
|
every { eventId } returns firstArg()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the latest event is a call invite and there is a live broadcast, when execute, returns the call event`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { root.type } returns EventType.MESSAGE
|
||||||
|
every { root.getClearType() } returns EventType.CALL_INVITE
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf(
|
||||||
|
givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "id1"),
|
||||||
|
givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "id1"),
|
||||||
|
).mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe aLatestPreviewableEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the latest event is not a call invite and there is a live broadcast, when execute, returns the latest broadcast event`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { root.type } returns EventType.MESSAGE
|
||||||
|
every { root.getClearType() } returns EventType.MESSAGE
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns listOf(
|
||||||
|
givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STARTED, "vb_id1"),
|
||||||
|
givenAVoiceBroadcastEvent("id2", VoiceBroadcastState.RESUMED, "vb_id2"),
|
||||||
|
).mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result?.eventId shouldBeEqualTo "vb_id2"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given there is no live broadcast, when execute, returns the latest event`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { root.type } returns EventType.MESSAGE
|
||||||
|
every { root.getClearType() } returns EventType.MESSAGE
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result shouldBe aLatestPreviewableEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given there is no live broadcast and the latest event is a vb message, when execute, returns null`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { root.type } returns EventType.MESSAGE
|
||||||
|
every { root.getClearType() } returns EventType.MESSAGE
|
||||||
|
every { root.getClearContent() } returns mapOf(
|
||||||
|
MessageContent.MSG_TYPE_JSON_KEY to "m.audio",
|
||||||
|
VOICE_BROADCAST_CHUNK_KEY to "1",
|
||||||
|
"body" to "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the latest event is an ended vb, when execute, returns the stopped event`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { eventId } returns "id1"
|
||||||
|
every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.STOPPED, "vb_id1")
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result?.eventId shouldBeEqualTo "id1"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given the latest event is a resumed vb, when execute, returns the started event`() {
|
||||||
|
// Given
|
||||||
|
val aLatestPreviewableEvent = mockk<TimelineEvent> {
|
||||||
|
every { eventId } returns "id1"
|
||||||
|
every { root } returns givenAVoiceBroadcastEvent("id1", VoiceBroadcastState.RESUMED, "vb_id1")
|
||||||
|
}
|
||||||
|
every { fakeRoomSummary.latestPreviewableEvent } returns aLatestPreviewableEvent
|
||||||
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(A_ROOM_ID) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getLatestPreviewableEventUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result?.eventId shouldBeEqualTo "vb_id1"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcastEvent(
|
||||||
|
eventId: String,
|
||||||
|
state: VoiceBroadcastState,
|
||||||
|
voiceBroadcastId: String,
|
||||||
|
): Event = mockk {
|
||||||
|
every { this@mockk.eventId } returns eventId
|
||||||
|
every { getClearType() } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
every { type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
every { content } returns mapOf(
|
||||||
|
"state" to state.value,
|
||||||
|
"m.relates_to" to mapOf(
|
||||||
|
"rel_type" to RelationType.REFERENCE,
|
||||||
|
"event_id" to voiceBroadcastId
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
/*
|
||||||
|
* 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 im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.amshove.kluent.shouldBeEqualTo
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.amshove.kluent.shouldNotBeNull
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "A_ROOM_ID"
|
||||||
|
private const val A_VOICE_BROADCAST_ID = "A_VOICE_BROADCAST_ID"
|
||||||
|
|
||||||
|
internal class GetVoiceBroadcastStateEventUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeSession = FakeSession()
|
||||||
|
private val getVoiceBroadcastStateEventUseCase = GetVoiceBroadcastStateEventUseCase(fakeSession)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given there is no event related to the given vb, when execute, then return null`() {
|
||||||
|
// Given
|
||||||
|
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(A_VOICE_BROADCAST_ID) } returns null
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns emptyList()
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given there are several related events related to the given vb, when execute, then return the most recent one`() {
|
||||||
|
// Given
|
||||||
|
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||||
|
val aListOfTimelineEvents = listOf(
|
||||||
|
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L),
|
||||||
|
givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.STOPPED, isRedacted = false, timestamp = 3L),
|
||||||
|
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L),
|
||||||
|
)
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldNotBeNull()
|
||||||
|
result.root.eventId shouldBeEqualTo "event_id_3"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given there are several related events related to the given vb, when execute, then return the most recent one which is not redacted`() {
|
||||||
|
// Given
|
||||||
|
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||||
|
val aListOfTimelineEvents = listOf(
|
||||||
|
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = false, timestamp = 1L),
|
||||||
|
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.STOPPED, isRedacted = true, timestamp = 2L),
|
||||||
|
)
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldNotBeNull()
|
||||||
|
result.root.eventId shouldBeEqualTo A_VOICE_BROADCAST_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a not ended voice broadcast with a redacted start event, when execute, then return null`() {
|
||||||
|
// Given
|
||||||
|
val aVoiceBroadcast = VoiceBroadcast(A_VOICE_BROADCAST_ID, A_ROOM_ID)
|
||||||
|
val aListOfTimelineEvents = listOf(
|
||||||
|
givenAVoiceBroadcastEvent(eventId = A_VOICE_BROADCAST_ID, state = VoiceBroadcastState.STARTED, isRedacted = true, timestamp = 1L),
|
||||||
|
givenAVoiceBroadcastEvent(eventId = "event_id_2", state = VoiceBroadcastState.PAUSED, isRedacted = false, timestamp = 2L),
|
||||||
|
givenAVoiceBroadcastEvent(eventId = "event_id_3", state = VoiceBroadcastState.RESUMED, isRedacted = false, timestamp = 3L),
|
||||||
|
)
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEventsRelatedTo(any(), any()) } returns aListOfTimelineEvents
|
||||||
|
|
||||||
|
// When
|
||||||
|
val result = getVoiceBroadcastStateEventUseCase.execute(aVoiceBroadcast)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
result.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcastEvent(
|
||||||
|
eventId: String,
|
||||||
|
state: VoiceBroadcastState,
|
||||||
|
isRedacted: Boolean,
|
||||||
|
timestamp: Long,
|
||||||
|
): TimelineEvent {
|
||||||
|
val timelineEvent = mockk<TimelineEvent> {
|
||||||
|
every { root.eventId } returns eventId
|
||||||
|
every { root.type } returns VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
every { root.content } returns mapOf("state" to state.value)
|
||||||
|
every { root.isRedacted() } returns isRedacted
|
||||||
|
every { root.originServerTs } returns timestamp
|
||||||
|
}
|
||||||
|
every { fakeSession.getRoom(A_ROOM_ID)?.timelineService()?.getTimelineEvent(eventId) } returns timelineEvent
|
||||||
|
return timelineEvent
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue