Skip to content

Commit b1d2581

Browse files
authored
Merge pull request #7898 from vector-im/bugfix/fre/unexpected_live_vb_room_list
Fix unexpected live voice broadcast in the room list
2 parents 3f9c11f + f62f661 commit b1d2581

File tree

8 files changed

+471
-34
lines changed

8 files changed

+471
-34
lines changed

changelog.d/7832.bugfix

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Voice Broadcast] Fix unexpected "live broadcast" in the room list

vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -22,41 +22,33 @@ import com.airbnb.mvrx.Loading
2222
import im.vector.app.R
2323
import im.vector.app.core.date.DateFormatKind
2424
import im.vector.app.core.date.VectorDateFormatter
25-
import im.vector.app.core.di.ActiveSessionHolder
2625
import im.vector.app.core.epoxy.VectorEpoxyModel
2726
import im.vector.app.core.error.ErrorFormatter
2827
import im.vector.app.core.resources.StringProvider
2928
import im.vector.app.features.home.AvatarRenderer
3029
import im.vector.app.features.home.RoomListDisplayMode
3130
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
31+
import im.vector.app.features.home.room.list.usecase.GetLatestPreviewableEventUseCase
3232
import im.vector.app.features.home.room.typing.TypingHelper
3333
import im.vector.app.features.voicebroadcast.isLive
34-
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
3534
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
36-
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
3735
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
3836
import org.matrix.android.sdk.api.extensions.orFalse
39-
import org.matrix.android.sdk.api.session.events.model.EventType
40-
import org.matrix.android.sdk.api.session.getRoom
41-
import org.matrix.android.sdk.api.session.room.getTimelineEvent
4237
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
4338
import org.matrix.android.sdk.api.session.room.model.Membership
4439
import org.matrix.android.sdk.api.session.room.model.RoomSummary
4540
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
46-
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
47-
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
4841
import org.matrix.android.sdk.api.util.toMatrixItem
4942
import javax.inject.Inject
5043

5144
class RoomSummaryItemFactory @Inject constructor(
52-
private val sessionHolder: ActiveSessionHolder,
5345
private val displayableEventFormatter: DisplayableEventFormatter,
5446
private val dateFormatter: VectorDateFormatter,
5547
private val stringProvider: StringProvider,
5648
private val typingHelper: TypingHelper,
5749
private val avatarRenderer: AvatarRenderer,
5850
private val errorFormatter: ErrorFormatter,
59-
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
51+
private val getLatestPreviewableEventUseCase: GetLatestPreviewableEventUseCase,
6052
) {
6153

6254
fun create(
@@ -142,15 +134,16 @@ class RoomSummaryItemFactory @Inject constructor(
142134
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
143135
var latestFormattedEvent: CharSequence = ""
144136
var latestEventTime = ""
145-
val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
137+
val latestEvent = getLatestPreviewableEventUseCase.execute(roomSummary.roomId)
146138
if (latestEvent != null) {
147139
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
148140
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
149141
}
150142

151143
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
152144
// Skip typing while there is a live voice broadcast
153-
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty()
145+
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }
146+
.orEmpty()
154147

155148
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
156149
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
@@ -240,14 +233,4 @@ class RoomSummaryItemFactory @Inject constructor(
240233
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
241234
}
242235
}
243-
244-
private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? {
245-
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent
246-
val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull()
247-
?.root?.eventId?.let { room.getTimelineEvent(it) }
248-
return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
249-
?: liveVoiceBroadcastTimelineEvent
250-
?: latestPreviewableEvent
251-
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
252-
}
253236
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.home.room.list.usecase
18+
19+
import im.vector.app.core.di.ActiveSessionHolder
20+
import im.vector.app.features.voicebroadcast.isLive
21+
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
22+
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
23+
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
24+
import im.vector.app.features.voicebroadcast.voiceBroadcastId
25+
import org.matrix.android.sdk.api.extensions.orFalse
26+
import org.matrix.android.sdk.api.session.events.model.EventType
27+
import org.matrix.android.sdk.api.session.getRoom
28+
import org.matrix.android.sdk.api.session.room.Room
29+
import org.matrix.android.sdk.api.session.room.getTimelineEvent
30+
import org.matrix.android.sdk.api.session.room.model.RoomSummary
31+
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
32+
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
33+
import javax.inject.Inject
34+
35+
class GetLatestPreviewableEventUseCase @Inject constructor(
36+
private val sessionHolder: ActiveSessionHolder,
37+
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
38+
) {
39+
40+
fun execute(roomId: String): TimelineEvent? {
41+
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return null
42+
val roomSummary = room.roomSummary() ?: return null
43+
return getCallEvent(roomSummary)
44+
?: getLiveVoiceBroadcastEvent(room)
45+
?: getDefaultLatestEvent(room, roomSummary)
46+
}
47+
48+
private fun getCallEvent(roomSummary: RoomSummary): TimelineEvent? {
49+
return roomSummary.latestPreviewableEvent
50+
?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
51+
}
52+
53+
private fun getLiveVoiceBroadcastEvent(room: Room): TimelineEvent? {
54+
return getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
55+
.lastOrNull()
56+
?.voiceBroadcastId
57+
?.let { room.getTimelineEvent(it) }
58+
}
59+
60+
private fun getDefaultLatestEvent(room: Room, roomSummary: RoomSummary): TimelineEvent? {
61+
val latestPreviewableEvent = roomSummary.latestPreviewableEvent
62+
63+
// If the default latest event is a live voice broadcast (paused or resumed), rely to the started event
64+
val liveVoiceBroadcastEventId = latestPreviewableEvent?.root?.asVoiceBroadcastEvent()?.takeIf { it.isLive }?.voiceBroadcastId
65+
if (liveVoiceBroadcastEventId != null) {
66+
return room.getTimelineEvent(liveVoiceBroadcastEventId)
67+
}
68+
69+
return latestPreviewableEvent
70+
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
71+
}
72+
}

vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetRoomLiveVoiceBroadcastsUseCase.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@ package im.vector.app.features.voicebroadcast.usecase
1919
import im.vector.app.core.di.ActiveSessionHolder
2020
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
2121
import im.vector.app.features.voicebroadcast.isLive
22+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
2223
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
2324
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
25+
import im.vector.app.features.voicebroadcast.voiceBroadcastId
2426
import org.matrix.android.sdk.api.query.QueryStringValue
2527
import org.matrix.android.sdk.api.session.getRoom
2628
import javax.inject.Inject
2729

30+
/**
31+
* Get the list of live (not ended) voice broadcast events in the given room.
32+
*/
2833
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
2934
private val activeSessionHolder: ActiveSessionHolder,
35+
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
3036
) {
3137

3238
fun execute(roomId: String): List<VoiceBroadcastEvent> {
@@ -37,7 +43,8 @@ class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
3743
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
3844
QueryStringValue.IsNotEmpty
3945
)
40-
.mapNotNull { it.asVoiceBroadcastEvent() }
46+
.mapNotNull { stateEvent -> stateEvent.asVoiceBroadcastEvent()?.voiceBroadcastId }
47+
.mapNotNull { voiceBroadcastId -> getVoiceBroadcastStateEventUseCase.execute(VoiceBroadcast(voiceBroadcastId, roomId)) }
4148
.filter { it.isLive }
4249
}
4350
}

vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetVoiceBroadcastStateEventLiveUseCase.kt

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ import kotlinx.coroutines.flow.onStart
3232
import kotlinx.coroutines.flow.transformWhile
3333
import org.matrix.android.sdk.api.query.QueryStringValue
3434
import org.matrix.android.sdk.api.session.Session
35-
import org.matrix.android.sdk.api.session.events.model.RelationType
3635
import org.matrix.android.sdk.api.session.getRoom
3736
import org.matrix.android.sdk.api.session.room.Room
3837
import org.matrix.android.sdk.api.util.Optional
@@ -44,6 +43,7 @@ import javax.inject.Inject
4443

4544
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
4645
private val session: Session,
46+
private val getVoiceBroadcastStateEventUseCase: GetVoiceBroadcastStateEventUseCase,
4747
) {
4848

4949
fun execute(voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
@@ -93,7 +93,7 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
9393
* Get a flow of the most recent related event.
9494
*/
9595
private fun getMostRecentRelatedEventFlow(room: Room, voiceBroadcast: VoiceBroadcast): Flow<Optional<VoiceBroadcastEvent>> {
96-
val mostRecentEvent = getMostRecentRelatedEvent(room, voiceBroadcast).toOptional()
96+
val mostRecentEvent = getVoiceBroadcastStateEventUseCase.execute(voiceBroadcast).toOptional()
9797
return if (mostRecentEvent.hasValue()) {
9898
val stateKey = mostRecentEvent.get().root.stateKey.orEmpty()
9999
// observe incoming voice broadcast state events
@@ -141,15 +141,6 @@ class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
141141
}
142142
}
143143

144-
/**
145-
* Get the most recent event related to the given voice broadcast.
146-
*/
147-
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
148-
return room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
149-
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent()?.takeUnless { it.root.isRedacted() } }
150-
.maxByOrNull { it.root.originServerTs ?: 0 }
151-
}
152-
153144
/**
154145
* Get a flow of the given voice broadcast event changes.
155146
*/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright (c) 2022 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package im.vector.app.features.voicebroadcast.usecase
18+
19+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
20+
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
21+
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
22+
import im.vector.app.features.voicebroadcast.voiceBroadcastId
23+
import org.matrix.android.sdk.api.extensions.orTrue
24+
import org.matrix.android.sdk.api.session.Session
25+
import org.matrix.android.sdk.api.session.events.model.RelationType
26+
import org.matrix.android.sdk.api.session.getRoom
27+
import org.matrix.android.sdk.api.session.room.Room
28+
import org.matrix.android.sdk.api.session.room.getTimelineEvent
29+
import timber.log.Timber
30+
import javax.inject.Inject
31+
32+
class GetVoiceBroadcastStateEventUseCase @Inject constructor(
33+
private val session: Session,
34+
) {
35+
36+
fun execute(voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
37+
val room = session.getRoom(voiceBroadcast.roomId) ?: error("Unknown roomId: ${voiceBroadcast.roomId}")
38+
return getMostRecentRelatedEvent(room, voiceBroadcast)
39+
.also { event ->
40+
Timber.d(
41+
"## VoiceBroadcast | " +
42+
"voiceBroadcastId=${event?.voiceBroadcastId}, " +
43+
"state=${event?.content?.voiceBroadcastState}"
44+
)
45+
}
46+
}
47+
48+
/**
49+
* Get the most recent event related to the given voice broadcast.
50+
*/
51+
private fun getMostRecentRelatedEvent(room: Room, voiceBroadcast: VoiceBroadcast): VoiceBroadcastEvent? {
52+
val startedEvent = room.getTimelineEvent(voiceBroadcast.voiceBroadcastId)
53+
return if (startedEvent?.root?.isRedacted().orTrue()) {
54+
null
55+
} else {
56+
room.timelineService().getTimelineEventsRelatedTo(RelationType.REFERENCE, voiceBroadcast.voiceBroadcastId)
57+
.mapNotNull { timelineEvent -> timelineEvent.root.asVoiceBroadcastEvent() }
58+
.filterNot { it.root.isRedacted() }
59+
.maxByOrNull { it.root.originServerTs ?: 0 }
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)