From b9bb7d789249daa644b6c12169d5a852bec4f510 Mon Sep 17 00:00:00 2001 From: Florian Renaud Date: Tue, 4 Oct 2022 15:13:01 +0200 Subject: [PATCH] Add VoiceBroadcastEvent wrapper --- .../model/VoiceBroadcastEvent.kt | 55 ++++++++ .../usecase/PauseVoiceBroadcastUseCase.kt | 24 ++-- .../usecase/ResumeVoiceBroadcastUseCase.kt | 29 +++-- .../usecase/StartVoiceBroadcastUseCase.kt | 9 +- .../usecase/StopVoiceBroadcastUseCase.kt | 23 ++-- .../model/VoiceBroadcastEventTest.kt | 123 ++++++++++++++++++ 6 files changed, 217 insertions(+), 46 deletions(-) create mode 100644 vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt create mode 100644 vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt new file mode 100644 index 0000000000..c09a5712a8 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEvent.kt @@ -0,0 +1,55 @@ +/* + * 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.model + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +import org.matrix.android.sdk.api.session.events.model.Event +import org.matrix.android.sdk.api.session.events.model.RelationType +import org.matrix.android.sdk.api.session.events.model.toModel +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent + +/** + * [Event] wrapper for [STATE_ROOM_VOICE_BROADCAST_INFO] event type. + * Provides additional fields and functions related to voice broadcast. + */ +@JvmInline +value class VoiceBroadcastEvent(val root: Event) { + + /** + * Reference on the initial voice broadcast state event (ie. with [MessageVoiceBroadcastInfoContent.voiceBroadcastState]=[VoiceBroadcastState.STARTED]). + */ + val reference: RelationDefaultContent? + get() { + val voiceBroadcastInfoContent = root.content.toModel() + return if (voiceBroadcastInfoContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { + RelationDefaultContent(RelationType.REFERENCE, root.eventId) + } else { + voiceBroadcastInfoContent?.relatesTo + } + } + + /** + * The mapped [MessageVoiceBroadcastInfoContent] model of the event content. + */ + val content: MessageVoiceBroadcastInfoContent? + get() = root.content.toModel() +} + +/** + * Map a [STATE_ROOM_VOICE_BROADCAST_INFO] state event to a [VoiceBroadcastEvent]. + */ +fun Event.asVoiceBroadcastEvent() = if (type == STATE_ROOM_VOICE_BROADCAST_INFO) VoiceBroadcastEvent(this) else null diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt index a7b544f69e..9c3fb3a3c3 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/PauseVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,28 +37,24 @@ class PauseVoiceBroadcastUseCase @Inject constructor( Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - val lastVoiceBroadcastInfoContent = lastVoiceBroadcastEvent?.content.toModel() - when (val voiceBroadcastState = lastVoiceBroadcastInfoContent?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STARTED, - VoiceBroadcastState.RESUMED -> pauseVoiceBroadcast(room, lastVoiceBroadcastEvent) + VoiceBroadcastState.RESUMED -> pauseVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## PauseVoiceBroadcastUseCase: Cannot pause voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun pauseVoiceBroadcast(room: Room, event: Event?) { + private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt index 294d91da0f..67839ccc5f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/ResumeVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,26 +37,29 @@ class ResumeVoiceBroadcastUseCase @Inject constructor( Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel()?.voiceBroadcastState) { - VoiceBroadcastState.PAUSED -> resumeVoiceBroadcast(room, lastVoiceBroadcastEvent) + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { + VoiceBroadcastState.PAUSED -> resumeVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## ResumeVoiceBroadcastUseCase: Cannot resume voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun resumeVoiceBroadcast(room: Room, event: Event?) { + /** + * Resume a paused voice broadcast in the given room. + * + * @param room the room related to the voice broadcast + * @param reference reference on the initial voice broadcast state event (ie. state=STARTED) + */ + private suspend fun resumeVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## ResumeVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value, ).toContent(), ) diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt index 3a9a8e1437..e423a35f26 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StartVoiceBroadcastUseCase.kt @@ -19,10 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import timber.log.Timber import javax.inject.Inject @@ -36,8 +36,11 @@ class StartVoiceBroadcastUseCase @Inject constructor( Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel()?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STOPPED, null -> startVoiceBroadcast(room) else -> Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentState=$voiceBroadcastState") diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt index 5a72f379a3..88a484c5a9 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/StopVoiceBroadcastUseCase.kt @@ -19,12 +19,10 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.events.model.Event -import org.matrix.android.sdk.api.session.events.model.RelationType import org.matrix.android.sdk.api.session.events.model.toContent -import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.room.Room import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent import timber.log.Timber @@ -39,28 +37,25 @@ class StopVoiceBroadcastUseCase @Inject constructor( Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested") - val lastVoiceBroadcastEvent = room.stateService().getStateEvent(STATE_ROOM_VOICE_BROADCAST_INFO, QueryStringValue.Equals(session.myUserId)) - when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content.toModel()?.voiceBroadcastState) { + val lastVoiceBroadcastEvent = room.stateService().getStateEvent( + STATE_ROOM_VOICE_BROADCAST_INFO, + QueryStringValue.Equals(session.myUserId) + )?.asVoiceBroadcastEvent() + when (val voiceBroadcastState = lastVoiceBroadcastEvent?.content?.voiceBroadcastState) { VoiceBroadcastState.STARTED, VoiceBroadcastState.PAUSED, - VoiceBroadcastState.RESUMED -> stopVoiceBroadcast(room, lastVoiceBroadcastEvent) + VoiceBroadcastState.RESUMED -> stopVoiceBroadcast(room, lastVoiceBroadcastEvent.reference) else -> Timber.d("## StopVoiceBroadcastUseCase: Cannot stop voice broadcast: currentState=$voiceBroadcastState") } } - private suspend fun stopVoiceBroadcast(room: Room, event: Event?) { + private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) { Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event") - val lastVoiceBroadcastContent = event?.content.toModel() - val relatesTo = if (lastVoiceBroadcastContent?.voiceBroadcastState == VoiceBroadcastState.STARTED) { - RelationDefaultContent(RelationType.REFERENCE, event?.eventId) - } else { - lastVoiceBroadcastContent?.relatesTo - } room.stateService().sendStateEvent( eventType = STATE_ROOM_VOICE_BROADCAST_INFO, stateKey = session.myUserId, body = MessageVoiceBroadcastInfoContent( - relatesTo = relatesTo, + relatesTo = reference, voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value, ).toContent(), ) diff --git a/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt new file mode 100644 index 0000000000..8865e870f0 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/voicebroadcast/model/VoiceBroadcastEventTest.kt @@ -0,0 +1,123 @@ +/* + * 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.model + +import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO +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.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.events.model.toContent +import org.matrix.android.sdk.api.session.room.model.message.AudioInfo +import org.matrix.android.sdk.api.session.room.model.message.AudioWaveformInfo +import org.matrix.android.sdk.api.session.room.model.message.MessageAudioContent +import org.matrix.android.sdk.api.session.room.model.message.MessageType +import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent +import org.matrix.android.sdk.api.session.room.model.relation.ReplyToContent + +private const val AN_EVENT_ID = "event_id" +private const val A_REFERENCED_EVENT_ID = "event_id_ref" +private const val A_CHUNK_LENGTH = 3_600L + +class VoiceBroadcastEventTest { + + @Test + fun `given a started Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return expected object`() { + // Given + val content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value, + chunkLength = A_CHUNK_LENGTH, + relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), + ) + val event = Event( + eventId = AN_EVENT_ID, + type = STATE_ROOM_VOICE_BROADCAST_INFO, + content = content.toContent(), + ) + val expectedReference = RelationDefaultContent(RelationType.REFERENCE, event.eventId) + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldNotBeNull() + result.content shouldBeEqualTo content + result.reference shouldBeEqualTo expectedReference + } + + @Test + fun `given a not started Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return expected object`() { + // Given + val content = MessageVoiceBroadcastInfoContent( + voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value, + chunkLength = A_CHUNK_LENGTH, + relatesTo = RelationDefaultContent(RelationType.REFERENCE, A_REFERENCED_EVENT_ID), + ) + val event = Event( + type = STATE_ROOM_VOICE_BROADCAST_INFO, + content = content.toContent(), + ) + val expectedReference = content.relatesTo + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldNotBeNull() + result.content shouldBeEqualTo content + result.reference shouldBeEqualTo expectedReference + } + + @Test + fun `given a non Voice Broadcast Event, when mapping to VoiceBroadcastEvent, then return null`() { + // Given + val content = MessageAudioContent( + msgType = MessageType.MSGTYPE_AUDIO, + body = "audio", + audioInfo = AudioInfo( + duration = 300, + mimeType = "", + size = 500L + ), + url = "a_url", + audioWaveformInfo = AudioWaveformInfo( + duration = 300, + waveform = null + ), + voiceMessageIndicator = emptyMap(), + relatesTo = RelationDefaultContent( + type = RelationType.THREAD, + eventId = AN_EVENT_ID, + isFallingBack = true, + inReplyTo = ReplyToContent(eventId = A_REFERENCED_EVENT_ID) + ) + ) + val event = Event( + type = EventType.MESSAGE, + content = content.toContent(), + ) + + // When + val result = event.asVoiceBroadcastEvent() + + // Then + result.shouldBeNull() + } +}