Merge pull request #7719 from vector-im/feature/fre/voice_broadcast_last_message

Voice Broadcast - Update last message in the room list
This commit is contained in:
Yoan Pintas 2022-12-14 17:39:42 +01:00 committed by GitHub
commit c74ea2dd16
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 242 additions and 73 deletions

1
changelog.d/7719.feature Normal file
View File

@ -0,0 +1 @@
Voice Broadcast - Update last message in the room list

View File

@ -134,6 +134,9 @@
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string> <string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string> <string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
<string name="notice_voice_broadcast_ended">%1$s ended a voice broadcast.</string>
<string name="notice_voice_broadcast_ended_by_you">You ended a voice broadcast.</string>
<!-- Messages --> <!-- Messages -->
<!-- Room Screen --> <!-- Room Screen -->
@ -3101,6 +3104,7 @@
<string name="audio_message_file_size">(%1$s)</string> <string name="audio_message_file_size">(%1$s)</string>
<string name="voice_broadcast_live">Live</string> <string name="voice_broadcast_live">Live</string>
<string name="voice_broadcast_live_broadcast">Live broadcast</string>
<!-- TODO Rename id to voice_broadcast_buffering --> <!-- TODO Rename id to voice_broadcast_buffering -->
<string name="a11y_voice_broadcast_buffering">Buffering…</string> <string name="a11y_voice_broadcast_buffering">Buffering…</string>
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string> <string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>

View File

@ -16,7 +16,7 @@
package org.matrix.android.sdk.common package org.matrix.android.sdk.common
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider { class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {

View File

@ -20,6 +20,9 @@ import okhttp3.ConnectionSpec
import okhttp3.Interceptor import okhttp3.Interceptor
import org.matrix.android.sdk.api.crypto.MXCryptoConfig import org.matrix.android.sdk.api.crypto.MXCryptoConfig
import org.matrix.android.sdk.api.metrics.MetricPlugin import org.matrix.android.sdk.api.metrics.MetricPlugin
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
import java.net.Proxy import java.net.Proxy
data class MatrixConfiguration( data class MatrixConfiguration(
@ -75,9 +78,12 @@ data class MatrixConfiguration(
* Sync configuration. * Sync configuration.
*/ */
val syncConfig: SyncConfig = SyncConfig(), val syncConfig: SyncConfig = SyncConfig(),
/** /**
* Metrics plugin that can be used to capture metrics from matrix-sdk-android. * Metrics plugin that can be used to capture metrics from matrix-sdk-android.
*/ */
val metricPlugins: List<MetricPlugin> = emptyList() val metricPlugins: List<MetricPlugin> = emptyList(),
/**
* CustomEventTypesProvider to provide custom event types to the sdk which should be processed with internal events.
*/
val customEventTypesProvider: CustomEventTypesProvider? = null,
) )

View File

@ -0,0 +1,30 @@
/*
* Copyright 2022 The Matrix.org Foundation C.I.C.
*
* 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 org.matrix.android.sdk.api.provider
import org.matrix.android.sdk.api.session.room.model.RoomSummary
/**
* Provide custom event types which should be processed with the internal event types.
*/
interface CustomEventTypesProvider {
/**
* Custom event types to include when computing [RoomSummary.latestPreviewableEvent].
*/
val customPreviewableEventTypes: List<String>
}

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.api package org.matrix.android.sdk.api.provider
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem

View File

@ -14,7 +14,7 @@
* limitations under the License. * limitations under the License.
*/ */
package org.matrix.android.sdk.api package org.matrix.android.sdk.api.provider
/** /**
* This interface exists to let the implementation provide localized room display name fallback. * This interface exists to let the implementation provide localized room display name fallback.

View File

@ -22,6 +22,7 @@ import io.realm.RealmQuery
import io.realm.RealmResults import io.realm.RealmResults
import io.realm.Sort import io.realm.Sort
import io.realm.kotlin.where import io.realm.kotlin.where
import org.matrix.android.sdk.api.session.events.model.EventType
import org.matrix.android.sdk.api.session.room.send.SendState import org.matrix.android.sdk.api.session.room.send.SendState
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
import org.matrix.android.sdk.internal.database.model.ChunkEntity import org.matrix.android.sdk.internal.database.model.ChunkEntity
@ -94,6 +95,18 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) { if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
beginGroup() beginGroup()
filters.allowedTypes.forEachIndexed { index, filter -> filters.allowedTypes.forEachIndexed { index, filter ->
if (filter.eventType == EventType.ENCRYPTED) {
val otherTypes = filters.allowedTypes.minus(filter).map { it.eventType }
if (filter.stateKey == null) {
filterEncryptedTypes(otherTypes)
} else {
beginGroup()
filterEncryptedTypes(otherTypes)
and()
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
endGroup()
}
} else {
if (filter.stateKey == null) { if (filter.stateKey == null) {
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType) equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
} else { } else {
@ -103,6 +116,7 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey) equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
endGroup() endGroup()
} }
}
if (index != filters.allowedTypes.size - 1) { if (index != filters.allowedTypes.size - 1) {
or() or()
} }
@ -115,7 +129,6 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
if (filters.filterEdits) { if (filters.filterEdits) {
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT) not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE) not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.REFERENCE)
} }
if (filters.filterRedacted) { if (filters.filterRedacted) {
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED) not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
@ -124,6 +137,21 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
return this return this
} }
internal fun RealmQuery<TimelineEventEntity>.filterEncryptedTypes(allowedTypes: List<String>): RealmQuery<TimelineEventEntity> {
beginGroup()
equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.ENCRYPTED)
and()
beginGroup()
isNull(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON)
allowedTypes.forEach { eventType ->
or()
like(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON, TimelineEventFilter.DecryptedContent.type(eventType))
}
endGroup()
endGroup()
return this
}
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> { internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
return if (filterTypes.isEmpty()) { return if (filterTypes.isEmpty()) {
this this

View File

@ -34,6 +34,7 @@ internal object TimelineEventFilter {
*/ */
internal object DecryptedContent { internal object DecryptedContent {
internal const val URL = """{*"file":*"url":*}""" internal const val URL = """{*"file":*"url":*}"""
fun type(type: String) = """{*"type":*"$type"*}"""
} }
/** /**

View File

@ -17,17 +17,23 @@
package org.matrix.android.sdk.internal.session.room.summary package org.matrix.android.sdk.internal.session.room.summary
import io.realm.Realm import io.realm.Realm
import org.matrix.android.sdk.api.MatrixConfiguration
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
import org.matrix.android.sdk.internal.database.query.latestEvent import org.matrix.android.sdk.internal.database.query.latestEvent
import javax.inject.Inject
internal object RoomSummaryEventsHelper { internal class RoomSummaryEventsHelper @Inject constructor(
matrixConfiguration: MatrixConfiguration,
) {
private val previewFilters = TimelineEventFilters( private val previewFilters = TimelineEventFilters(
filterTypes = true, filterTypes = true,
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES.map { EventTypeFilter(eventType = it, stateKey = null) }, allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES
.plus(matrixConfiguration.customEventTypesProvider?.customPreviewableEventTypes.orEmpty())
.map { EventTypeFilter(eventType = it, stateKey = null) },
filterUseless = true, filterUseless = true,
filterRedacted = false, filterRedacted = false,
filterEdits = true filterEdits = true

View File

@ -78,12 +78,13 @@ internal class RoomSummaryUpdater @Inject constructor(
private val crossSigningService: DefaultCrossSigningService, private val crossSigningService: DefaultCrossSigningService,
private val roomAccountDataDataSource: RoomAccountDataDataSource, private val roomAccountDataDataSource: RoomAccountDataDataSource,
private val homeServerCapabilitiesService: HomeServerCapabilitiesService, private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
private val roomSummaryEventsHelper: RoomSummaryEventsHelper,
) { ) {
fun refreshLatestPreviewContent(realm: Realm, roomId: String) { fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId) val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
if (roomSummaryEntity != null) { if (roomSummaryEntity != null) {
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
latestPreviewableEvent?.attemptToDecrypt() latestPreviewableEvent?.attemptToDecrypt()
} }
} }
@ -145,7 +146,7 @@ internal class RoomSummaryUpdater @Inject constructor(
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent") Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
if (lastActivityFromEvent != null) { if (lastActivityFromEvent != null) {
@ -231,7 +232,7 @@ internal class RoomSummaryUpdater @Inject constructor(
fun updateSendingInformation(realm: Realm, roomId: String) { fun updateSendingInformation(realm: Realm, roomId: String) {
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId) val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
roomSummaryEntity.updateHasFailedSending() roomSummaryEntity.updateHasFailedSending()
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId) roomSummaryEntity.latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
} }
/** /**

View File

@ -49,6 +49,7 @@ import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.errors.ErrorTracker import im.vector.app.features.analytics.errors.ErrorTracker
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
import im.vector.app.features.analytics.metrics.VectorPlugins import im.vector.app.features.analytics.metrics.VectorPlugins
import im.vector.app.features.configuration.VectorCustomEventTypesProvider
import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.AutoAcceptInvites
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
import im.vector.app.features.navigation.DefaultNavigator import im.vector.app.features.navigation.DefaultNavigator
@ -145,6 +146,7 @@ import javax.inject.Singleton
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider, vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
flipperProxy: FlipperProxy, flipperProxy: FlipperProxy,
vectorPlugins: VectorPlugins, vectorPlugins: VectorPlugins,
vectorCustomEventTypesProvider: VectorCustomEventTypesProvider,
): MatrixConfiguration { ): MatrixConfiguration {
return MatrixConfiguration( return MatrixConfiguration(
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION, applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
@ -154,6 +156,7 @@ import javax.inject.Singleton
flipperProxy.networkInterceptor(), flipperProxy.networkInterceptor(),
), ),
metricPlugins = vectorPlugins.plugins(), metricPlugins = vectorPlugins.plugins(),
customEventTypesProvider = vectorCustomEventTypesProvider,
) )
} }

View File

@ -27,7 +27,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
import javax.inject.Singleton import javax.inject.Singleton
@InstallIn(SingletonComponent::class) @InstallIn(SingletonComponent::class)
@ -40,13 +40,13 @@ abstract class VoiceModule {
fun providesVoiceBroadcastRecorder( fun providesVoiceBroadcastRecorder(
context: Context, context: Context,
sessionHolder: ActiveSessionHolder, sessionHolder: ActiveSessionHolder,
getMostRecentVoiceBroadcastStateEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase, getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase,
): VoiceBroadcastRecorder? { ): VoiceBroadcastRecorder? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
VoiceBroadcastRecorderQ( VoiceBroadcastRecorderQ(
context = context, context = context,
sessionHolder = sessionHolder, sessionHolder = sessionHolder,
getVoiceBroadcastEventUseCase = getMostRecentVoiceBroadcastStateEventUseCase getVoiceBroadcastEventUseCase = getVoiceBroadcastStateEventLiveUseCase
) )
} else { } else {
null null

View File

@ -0,0 +1,28 @@
/*
* 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.configuration
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
import javax.inject.Inject
class VectorCustomEventTypesProvider @Inject constructor() : CustomEventTypesProvider {
override val customPreviewableEventTypes = listOf(
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO
)
}

View File

@ -16,7 +16,7 @@
package im.vector.app.features.displayname package im.vector.app.features.displayname
import org.matrix.android.sdk.api.MatrixItemDisplayNameFallbackProvider import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
import org.matrix.android.sdk.api.util.MatrixItem import org.matrix.android.sdk.api.util.MatrixItem
// Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration // Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration

View File

@ -20,9 +20,15 @@ import dagger.Lazy
import im.vector.app.EmojiSpanify import im.vector.app.EmojiSpanify
import im.vector.app.R import im.vector.app.R
import im.vector.app.core.extensions.getVectorLastMessageContent import im.vector.app.core.extensions.getVectorLastMessageContent
import im.vector.app.core.extensions.orEmpty
import im.vector.app.core.resources.ColorProvider import im.vector.app.core.resources.ColorProvider
import im.vector.app.core.resources.DrawableProvider
import im.vector.app.core.resources.StringProvider import im.vector.app.core.resources.StringProvider
import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.EventHtmlRenderer
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.isLive
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import me.gujun.android.span.image
import me.gujun.android.span.span import me.gujun.android.span.span
import org.commonmark.node.Document import org.commonmark.node.Document
import org.matrix.android.sdk.api.session.events.model.Event import org.matrix.android.sdk.api.session.events.model.Event
@ -41,6 +47,7 @@ import javax.inject.Inject
class DisplayableEventFormatter @Inject constructor( class DisplayableEventFormatter @Inject constructor(
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val drawableProvider: DrawableProvider,
private val emojiSpanify: EmojiSpanify, private val emojiSpanify: EmojiSpanify,
private val noticeEventFormatter: NoticeEventFormatter, private val noticeEventFormatter: NoticeEventFormatter,
private val htmlRenderer: Lazy<EventHtmlRenderer> private val htmlRenderer: Lazy<EventHtmlRenderer>
@ -135,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor(
in EventType.STATE_ROOM_BEACON_INFO.values -> { in EventType.STATE_ROOM_BEACON_INFO.values -> {
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor) simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
} }
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
formatVoiceBroadcastEvent(timelineEvent.root, isDm, senderName)
}
else -> { else -> {
span { span {
text = noticeEventFormatter.format(timelineEvent, isDm) ?: "" text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
@ -252,4 +262,20 @@ class DisplayableEventFormatter @Inject constructor(
body body
} }
} }
private fun formatVoiceBroadcastEvent(event: Event, isDm: Boolean, senderName: String): CharSequence {
return if (event.asVoiceBroadcastEvent()?.isLive == true) {
span {
drawableProvider.getDrawable(R.drawable.ic_voice_broadcast, colorProvider.getColor(R.color.palette_vermilion))?.let {
image(it)
+" "
}
span(stringProvider.getString(R.string.voice_broadcast_live_broadcast)) {
textColor = colorProvider.getColor(R.color.palette_vermilion)
}
}
} else {
noticeEventFormatter.format(event, senderName, isDm).orEmpty()
}
}
} }

View File

@ -22,6 +22,8 @@ import im.vector.app.core.resources.StringProvider
import im.vector.app.features.roomprofile.permissions.RoleFormatter import im.vector.app.features.roomprofile.permissions.RoleFormatter
import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorPreferences
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
import org.matrix.android.sdk.api.extensions.appendNl import org.matrix.android.sdk.api.extensions.appendNl
import org.matrix.android.sdk.api.extensions.orFalse import org.matrix.android.sdk.api.extensions.orFalse
@ -67,30 +69,32 @@ class NoticeEventFormatter @Inject constructor(
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? { fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? {
return when (val type = timelineEvent.root.getClearType()) { val event = timelineEvent.root
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, isDm) return when (val type = event.getClearType()) {
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, isDm)
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(event, isDm)
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName, isDm)
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName, isDm)
EventType.STATE_ROOM_HISTORY_VISIBILITY -> EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(event, senderName)
formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(event, senderName)
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName, isDm)
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(event, senderName)
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(event, senderName, isDm)
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(event, senderName)
EventType.STATE_ROOM_WIDGET, EventType.STATE_ROOM_WIDGET,
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(event, senderName)
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm) EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(event, senderName)
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_CANDIDATES, EventType.CALL_CANDIDATES,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_REJECT, EventType.CALL_REJECT,
EventType.CALL_ANSWER -> formatCallEvent(type, timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName) EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
EventType.CALL_NEGOTIATE, EventType.CALL_NEGOTIATE,
EventType.CALL_SELECT_ANSWER, EventType.CALL_SELECT_ANSWER,
EventType.CALL_REPLACES, EventType.CALL_REPLACES,
@ -109,8 +113,7 @@ class NoticeEventFormatter @Inject constructor(
EventType.STICKER, EventType.STICKER,
in EventType.POLL_RESPONSE.values, in EventType.POLL_RESPONSE.values,
in EventType.POLL_END.values, in EventType.POLL_END.values,
in EventType.BEACON_LOCATION_DATA.values, in EventType.BEACON_LOCATION_DATA.values -> formatDebug(event)
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatDebug(timelineEvent.root)
else -> { else -> {
Timber.v("Type $type not handled by this formatter") Timber.v("Type $type not handled by this formatter")
null null
@ -191,6 +194,7 @@ class NoticeEventFormatter @Inject constructor(
EventType.CALL_REJECT, EventType.CALL_REJECT,
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName) EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm) EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
else -> { else -> {
Timber.v("Type $type not handled by this formatter") Timber.v("Type $type not handled by this formatter")
null null
@ -894,4 +898,16 @@ class NoticeEventFormatter @Inject constructor(
} }
} }
} }
private fun formatVoiceBroadcastEvent(event: Event, senderName: String?): CharSequence {
return if (event.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED) {
if (event.isSentByCurrentUser()) {
sp.getString(R.string.notice_voice_broadcast_ended_by_you)
} else {
sp.getString(R.string.notice_voice_broadcast_ended, senderName)
}
} else {
formatDebug(event)
}
}
} }

View File

@ -252,7 +252,7 @@ class TimelineEventVisibilityHelper @Inject constructor(
} }
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO && if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState != VoiceBroadcastState.STARTED) { root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState !in arrayOf(VoiceBroadcastState.STARTED, VoiceBroadcastState.STOPPED)) {
return true return true
} }

View File

@ -22,6 +22,7 @@ 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
@ -29,21 +30,33 @@ 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.typing.TypingHelper import im.vector.app.features.home.room.typing.TypingHelper
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.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.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,
) { ) {
fun create( fun create(
@ -129,13 +142,15 @@ 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.latestPreviewableEvent val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
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)
} }
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
// Skip typing while there is a live voice broadcast
.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)
@ -225,4 +240,14 @@ 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
}
} }

View File

@ -18,7 +18,7 @@ package im.vector.app.features.room
import android.content.Context import android.content.Context
import im.vector.app.R import im.vector.app.R
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
import javax.inject.Inject import javax.inject.Inject
class VectorRoomDisplayNameFallbackProvider @Inject constructor( class VectorRoomDisplayNameFallbackProvider @Inject constructor(

View File

@ -31,7 +31,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Stat
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast 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.usecase.GetMostRecentVoiceBroadcastStateEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
import im.vector.lib.core.utils.timer.CountUpTimer import im.vector.lib.core.utils.timer.CountUpTimer
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -48,7 +48,7 @@ import javax.inject.Singleton
class VoiceBroadcastPlayerImpl @Inject constructor( class VoiceBroadcastPlayerImpl @Inject constructor(
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
private val playbackTracker: AudioMessagePlaybackTracker, private val playbackTracker: AudioMessagePlaybackTracker,
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase, private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
) : VoiceBroadcastPlayer { ) : VoiceBroadcastPlayer {

View File

@ -24,7 +24,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.sequence import im.vector.app.features.voicebroadcast.sequence
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
import im.vector.app.features.voicebroadcast.voiceBroadcastId import im.vector.app.features.voicebroadcast.voiceBroadcastId
import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -48,7 +48,7 @@ import javax.inject.Inject
*/ */
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor( class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase, private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
) { ) {
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> { fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {

View File

@ -26,7 +26,7 @@ import im.vector.app.features.voice.AbstractVoiceRecorderQ
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast 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.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
import im.vector.lib.core.utils.timer.CountUpTimer import im.vector.lib.core.utils.timer.CountUpTimer
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit
class VoiceBroadcastRecorderQ( class VoiceBroadcastRecorderQ(
context: Context, context: Context,
private val sessionHolder: ActiveSessionHolder, private val sessionHolder: ActiveSessionHolder,
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder { ) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
private val session get() = sessionHolder.getActiveSession() private val session get() = sessionHolder.getActiveSession()

View File

@ -28,7 +28,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
import im.vector.lib.multipicker.utils.toMultiPickerAudioType import im.vector.lib.multipicker.utils.toMultiPickerAudioType
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -56,7 +56,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?, private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
private val context: Context, private val context: Context,
private val buildMeta: BuildMeta, private val buildMeta: BuildMeta,
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase, private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
) { ) {
@ -152,7 +152,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast") Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast")
throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting
} }
getOngoingVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> { getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> {
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting") Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting")
throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse
} }

View File

@ -19,7 +19,7 @@ package im.vector.app.features.voicebroadcast.recording.usecase
import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ActiveSessionHolder
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
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 org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.Membership
@ -32,7 +32,7 @@ import javax.inject.Inject
*/ */
class StopOngoingVoiceBroadcastUseCase @Inject constructor( class StopOngoingVoiceBroadcastUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
private val voiceBroadcastHelper: VoiceBroadcastHelper, private val voiceBroadcastHelper: VoiceBroadcastHelper,
) { ) {
@ -53,7 +53,7 @@ class StopOngoingVoiceBroadcastUseCase @Inject constructor(
recentRooms recentRooms
.forEach { room -> .forEach { room ->
val ongoingVoiceBroadcasts = getOngoingVoiceBroadcastsUseCase.execute(room.roomId) val ongoingVoiceBroadcasts = getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId
val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() } val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() }
if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) { if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) {

View File

@ -18,32 +18,26 @@ 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.model.VoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
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 timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class GetOngoingVoiceBroadcastsUseCase @Inject constructor( class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
private val activeSessionHolder: ActiveSessionHolder, private val activeSessionHolder: ActiveSessionHolder,
) { ) {
fun execute(roomId: String): List<VoiceBroadcastEvent> { fun execute(roomId: String): List<VoiceBroadcastEvent> {
val session = activeSessionHolder.getSafeActiveSession() ?: run { val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList()
Timber.d("## GetOngoingVoiceBroadcastsUseCase: no active session")
return emptyList()
}
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId") val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId")
return room.stateService().getStateEvents( return room.stateService().getStateEvents(
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO), setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
QueryStringValue.IsNotEmpty QueryStringValue.IsNotEmpty
) )
.mapNotNull { it.asVoiceBroadcastEvent() } .mapNotNull { it.asVoiceBroadcastEvent() }
.filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } .filter { it.isLive }
} }
} }

View File

@ -42,7 +42,7 @@ import org.matrix.android.sdk.flow.mapOptional
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
class GetMostRecentVoiceBroadcastStateEventUseCase @Inject constructor( class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
private val session: Session, private val session: Session,
) { ) {

View File

@ -52,14 +52,14 @@ class StartVoiceBroadcastUseCaseTest {
private val fakeRoom = FakeRoom() private val fakeRoom = FakeRoom()
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom)) private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true) private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
private val fakeGetOngoingVoiceBroadcastsUseCase = mockk<GetOngoingVoiceBroadcastsUseCase>() private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk<GetRoomLiveVoiceBroadcastsUseCase>()
private val startVoiceBroadcastUseCase = spyk( private val startVoiceBroadcastUseCase = spyk(
StartVoiceBroadcastUseCase( StartVoiceBroadcastUseCase(
session = fakeSession, session = fakeSession,
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder, voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
context = FakeContext().instance, context = FakeContext().instance,
buildMeta = mockk(), buildMeta = mockk(),
getOngoingVoiceBroadcastsUseCase = fakeGetOngoingVoiceBroadcastsUseCase, getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
stopVoiceBroadcastUseCase = mockk() stopVoiceBroadcastUseCase = mockk()
) )
) )
@ -140,7 +140,7 @@ class StartVoiceBroadcastUseCaseTest {
} }
.mapNotNull { it.asVoiceBroadcastEvent() } .mapNotNull { it.asVoiceBroadcastEvent() }
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } .filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
every { fakeGetOngoingVoiceBroadcastsUseCase.execute(any()) } returns events every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(any()) } returns events
} }
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState) private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)