Merge pull request #7273 from vector-im/feature/fre/voice_broadcast_state_event
Voice Broadcast - Send state events
This commit is contained in:
commit
48d2cc4745
1
changelog.d/7273.wip
Normal file
1
changelog.d/7273.wip
Normal file
@ -0,0 +1 @@
|
|||||||
|
[Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget
|
@ -43,4 +43,7 @@ object MessageType {
|
|||||||
// Fake message types for live location events to be able to inherit them from MessageContent
|
// Fake message types for live location events to be able to inherit them from MessageContent
|
||||||
const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
|
const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
|
||||||
const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
|
const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
|
||||||
|
|
||||||
|
// Fake message types for voice broadcast events to be able to inherit them from MessageContent
|
||||||
|
const val MSGTYPE_VOICE_BROADCAST_INFO = "io.element.voicebroadcast.info"
|
||||||
}
|
}
|
||||||
|
@ -16,9 +16,14 @@
|
|||||||
|
|
||||||
package im.vector.app.core.extensions
|
package im.vector.app.core.extensions
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
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.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
||||||
|
|
||||||
fun TimelineEvent.canReact(): Boolean {
|
fun TimelineEvent.canReact(): Boolean {
|
||||||
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
|
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
|
||||||
@ -26,3 +31,15 @@ fun TimelineEvent.canReact(): Boolean {
|
|||||||
root.sendState == SendState.SYNCED &&
|
root.sendState == SendState.SYNCED &&
|
||||||
!root.isRedacted()
|
!root.isRedacted()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get last MessageContent, after a possible edition.
|
||||||
|
* This method iterate on the vector event types and fallback to [getLastMessageContent] from the matrix sdk for the other types.
|
||||||
|
*/
|
||||||
|
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
|
||||||
|
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
|
||||||
|
return when (root.getClearType()) {
|
||||||
|
STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
else -> getLastMessageContent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -79,7 +79,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
|
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
|
||||||
|
|
||||||
object SelectStickerAttachment : RoomDetailAction()
|
object SelectStickerAttachment : RoomDetailAction()
|
||||||
object StartVoiceBroadcast : RoomDetailAction()
|
|
||||||
object OpenIntegrationManager : RoomDetailAction()
|
object OpenIntegrationManager : RoomDetailAction()
|
||||||
object ManageIntegrations : RoomDetailAction()
|
object ManageIntegrations : RoomDetailAction()
|
||||||
data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
|
data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
|
||||||
@ -120,4 +119,11 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
object StopLiveLocationSharing : RoomDetailAction()
|
object StopLiveLocationSharing : RoomDetailAction()
|
||||||
|
|
||||||
object OpenElementCallWidget : RoomDetailAction()
|
object OpenElementCallWidget : RoomDetailAction()
|
||||||
|
|
||||||
|
sealed class VoiceBroadcastAction : RoomDetailAction() {
|
||||||
|
object Start : VoiceBroadcastAction()
|
||||||
|
object Pause : VoiceBroadcastAction()
|
||||||
|
object Resume : VoiceBroadcastAction()
|
||||||
|
object Stop : VoiceBroadcastAction()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ import im.vector.app.features.raw.wellknown.withElementWellKnown
|
|||||||
import im.vector.app.features.session.coroutineScope
|
import im.vector.app.features.session.coroutineScope
|
||||||
import im.vector.app.features.settings.VectorDataStore
|
import im.vector.app.features.settings.VectorDataStore
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
||||||
import im.vector.lib.core.utils.flow.chunk
|
import im.vector.lib.core.utils.flow.chunk
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
@ -149,6 +150,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
buildMeta: BuildMeta,
|
buildMeta: BuildMeta,
|
||||||
timelineFactory: TimelineFactory,
|
timelineFactory: TimelineFactory,
|
||||||
private val spaceStateHandler: SpaceStateHandler,
|
private val spaceStateHandler: SpaceStateHandler,
|
||||||
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
|
||||||
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
|
||||||
|
|
||||||
@ -456,7 +458,7 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
|
||||||
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
|
||||||
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
|
||||||
is RoomDetailAction.StartVoiceBroadcast -> handleStartVoiceBroadcast()
|
is RoomDetailAction.VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
|
||||||
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
|
||||||
is RoomDetailAction.StartCall -> handleStartCall(action)
|
is RoomDetailAction.StartCall -> handleStartCall(action)
|
||||||
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
|
||||||
@ -598,9 +600,16 @@ class TimelineViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleStartVoiceBroadcast() {
|
private fun handleVoiceBroadcastAction(action: RoomDetailAction.VoiceBroadcastAction) {
|
||||||
// Todo implement start voice broadcast action
|
if (room == null) return
|
||||||
Timber.d("Start voice broadcast clicked")
|
viewModelScope.launch {
|
||||||
|
when (action) {
|
||||||
|
RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
|
||||||
|
RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
|
||||||
|
RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
|
||||||
|
RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleOpenIntegrationManager() {
|
private fun handleOpenIntegrationManager() {
|
||||||
|
@ -48,6 +48,7 @@ import com.vanniktech.emoji.EmojiPopup
|
|||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.error.fatalError
|
import im.vector.app.core.error.fatalError
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.extensions.registerStartForActivityResult
|
import im.vector.app.core.extensions.registerStartForActivityResult
|
||||||
import im.vector.app.core.extensions.showKeyboard
|
import im.vector.app.core.extensions.showKeyboard
|
||||||
import im.vector.app.core.glide.GlideApp
|
import im.vector.app.core.glide.GlideApp
|
||||||
@ -73,6 +74,7 @@ import im.vector.app.features.command.ParsedCommand
|
|||||||
import im.vector.app.features.home.AvatarRenderer
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.room.detail.AutoCompleter
|
import im.vector.app.features.home.room.detail.AutoCompleter
|
||||||
import im.vector.app.features.home.room.detail.RoomDetailAction
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
|
||||||
import im.vector.app.features.home.room.detail.TimelineViewModel
|
import im.vector.app.features.home.room.detail.TimelineViewModel
|
||||||
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
|
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
|
||||||
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
import im.vector.app.features.home.room.detail.timeline.action.MessageSharedActionViewModel
|
||||||
@ -102,7 +104,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageFormat
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import reactivecircus.flowbinding.android.view.focusChanges
|
import reactivecircus.flowbinding.android.view.focusChanges
|
||||||
@ -355,7 +356,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||||||
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
|
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageContent: MessageContent? = event.getLastMessageContent()
|
val messageContent: MessageContent? = event.getVectorLastMessageContent()
|
||||||
val nonFormattedBody = when (messageContent) {
|
val nonFormattedBody = when (messageContent) {
|
||||||
is MessageAudioContent -> getAudioContentBodyText(messageContent)
|
is MessageAudioContent -> getAudioContentBodyText(messageContent)
|
||||||
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
|
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
|
||||||
@ -653,7 +654,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
|
|||||||
locationOwnerId = session.myUserId
|
locationOwnerId = session.myUserId
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast)
|
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.analytics.AnalyticsTracker
|
import im.vector.app.features.analytics.AnalyticsTracker
|
||||||
@ -62,7 +63,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|||||||
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
|
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
|
||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
import org.matrix.android.sdk.api.session.room.send.UserDraft
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
|
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
|
||||||
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
|
||||||
@ -513,7 +513,7 @@ class MessageComposerViewModel @AssistedInject constructor(
|
|||||||
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
|
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
|
val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent()
|
||||||
val existingBody = messageContent?.body ?: ""
|
val existingBody = messageContent?.body ?: ""
|
||||||
if (existingBody != action.text) {
|
if (existingBody != action.text) {
|
||||||
room.relationService().editTextMessage(
|
room.relationService().editTextMessage(
|
||||||
|
@ -17,6 +17,7 @@
|
|||||||
package im.vector.app.features.home.room.detail.timeline.action
|
package im.vector.app.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -27,8 +28,13 @@ class CheckIfCanRedactEventUseCase @Inject constructor(
|
|||||||
|
|
||||||
fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
|
||||||
// Only some event types are supported for the moment
|
// Only some event types are supported for the moment
|
||||||
val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) +
|
val canRedactEventTypes: List<String> = listOf(
|
||||||
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
EventType.MESSAGE,
|
||||||
|
EventType.STICKER,
|
||||||
|
STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
) +
|
||||||
|
EventType.POLL_START +
|
||||||
|
EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
||||||
return event.root.getClearType() in canRedactEventTypes &&
|
return event.root.getClearType() in canRedactEventTypes &&
|
||||||
// Message sent by the current user can always be redacted, else check permission for messages sent by other users
|
// Message sent by the current user can always be redacted, else check permission for messages sent by other users
|
||||||
|
@ -25,6 +25,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
|||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.extensions.canReact
|
import im.vector.app.core.extensions.canReact
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.platform.EmptyViewEvents
|
import im.vector.app.core.platform.EmptyViewEvents
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
@ -60,7 +61,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
|
|||||||
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
|
||||||
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.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
|
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.isPoll
|
import org.matrix.android.sdk.api.session.room.timeline.isPoll
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
|
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
|
||||||
@ -187,7 +187,7 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||||||
when (timelineEvent.root.getClearType()) {
|
when (timelineEvent.root.getClearType()) {
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STICKER -> {
|
EventType.STICKER -> {
|
||||||
val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
|
val messageContent: MessageContent? = timelineEvent.getVectorLastMessageContent()
|
||||||
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
|
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
|
||||||
val html = messageContent.formattedBody
|
val html = messageContent.formattedBody
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
@ -253,7 +253,7 @@ class MessageActionsViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
|
||||||
val messageContent = timelineEvent.getLastMessageContent()
|
val messageContent = timelineEvent.getVectorLastMessageContent()
|
||||||
val msgType = messageContent?.msgType
|
val msgType = messageContent?.msgType
|
||||||
|
|
||||||
return arrayListOf<EventSharedAction>().apply {
|
return arrayListOf<EventSharedAction>().apply {
|
||||||
|
@ -28,6 +28,7 @@ import dagger.Lazy
|
|||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.epoxy.ClickListener
|
import im.vector.app.core.epoxy.ClickListener
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.files.LocalFilesHelper
|
import im.vector.app.core.files.LocalFilesHelper
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
@ -55,6 +56,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem
|
|||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.PollItem
|
import im.vector.app.features.home.room.detail.timeline.item.PollItem
|
||||||
@ -77,6 +80,7 @@ import im.vector.app.features.media.ImageContentRenderer
|
|||||||
import im.vector.app.features.media.VideoContentRenderer
|
import im.vector.app.features.media.VideoContentRenderer
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.voice.AudioWaveformView
|
import im.vector.app.features.voice.AudioWaveformView
|
||||||
|
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
|
||||||
@ -102,7 +106,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
|
||||||
import org.matrix.android.sdk.api.util.MimeTypes
|
import org.matrix.android.sdk.api.util.MimeTypes
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -163,7 +166,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
return buildRedactedItem(attributes, highlight)
|
return buildRedactedItem(attributes, highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
val messageContent = event.getLastMessageContent()
|
val messageContent = event.getVectorLastMessageContent()
|
||||||
if (messageContent == null) {
|
if (messageContent == null) {
|
||||||
val malformedText = stringProvider.getString(R.string.malformed_message)
|
val malformedText = stringProvider.getString(R.string.malformed_message)
|
||||||
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
|
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
|
||||||
@ -197,6 +200,7 @@ class MessageItemFactory @Inject constructor(
|
|||||||
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
|
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
|
||||||
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
|
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
|
||||||
|
is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, highlight, callback, attributes)
|
||||||
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
|
||||||
}
|
}
|
||||||
return messageItem?.apply {
|
return messageItem?.apply {
|
||||||
@ -706,6 +710,20 @@ class MessageItemFactory @Inject constructor(
|
|||||||
.highlighted(highlight)
|
.highlighted(highlight)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun buildVoiceBroadcastItem(
|
||||||
|
messageContent: MessageVoiceBroadcastInfoContent,
|
||||||
|
highlight: Boolean,
|
||||||
|
callback: TimelineEventController.Callback?,
|
||||||
|
attributes: AbsMessageItem.Attributes,
|
||||||
|
): MessageVoiceBroadcastItem? {
|
||||||
|
return MessageVoiceBroadcastItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.highlighted(highlight)
|
||||||
|
.voiceBroadcastState(messageContent.voiceBroadcastState)
|
||||||
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
|
.callback(callback)
|
||||||
|
}
|
||||||
|
|
||||||
private fun List<Int?>?.toFft(): List<Int>? {
|
private fun List<Int?>?.toFft(): List<Int>? {
|
||||||
return this
|
return this
|
||||||
?.filterNotNull()
|
?.filterNotNull()
|
||||||
|
@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_
|
|||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.features.analytics.DecryptionFailureTracker
|
import im.vector.app.features.analytics.DecryptionFailureTracker
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -88,6 +89,7 @@ class TimelineItemFactory @Inject constructor(
|
|||||||
// State room create
|
// State room create
|
||||||
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
|
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
|
||||||
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
|
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
|
||||||
|
STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params)
|
||||||
// Unhandled state event types
|
// Unhandled state event types
|
||||||
else -> {
|
else -> {
|
||||||
// Should only happen when shouldShowHiddenEvents() settings is ON
|
// Should only happen when shouldShowHiddenEvents() settings is ON
|
||||||
|
@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format
|
|||||||
import dagger.Lazy
|
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.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
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
|
||||||
@ -34,7 +35,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent
|
import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -60,7 +60,7 @@ class DisplayableEventFormatter @Inject constructor(
|
|||||||
|
|
||||||
return when (timelineEvent.root.getClearType()) {
|
return when (timelineEvent.root.getClearType()) {
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
timelineEvent.getLastMessageContent()?.let { messageContent ->
|
timelineEvent.getVectorLastMessageContent()?.let { messageContent ->
|
||||||
when (messageContent.msgType) {
|
when (messageContent.msgType) {
|
||||||
MessageType.MSGTYPE_TEXT -> {
|
MessageType.MSGTYPE_TEXT -> {
|
||||||
val body = messageContent.getTextDisplayableContent()
|
val body = messageContent.getTextDisplayableContent()
|
||||||
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.helper
|
|||||||
|
|
||||||
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.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
import im.vector.app.features.home.room.detail.timeline.factory.TimelineItemFactoryParams
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
import im.vector.app.features.home.room.detail.timeline.item.E2EDecoration
|
||||||
@ -41,7 +42,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
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.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
|
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -123,7 +123,11 @@ class MessageInformationDataFactory @Inject constructor(
|
|||||||
isLastFromThisSender = isLastFromThisSender,
|
isLastFromThisSender = isLastFromThisSender,
|
||||||
e2eDecoration = e2eDecoration,
|
e2eDecoration = e2eDecoration,
|
||||||
sendStateDecoration = sendStateDecoration,
|
sendStateDecoration = sendStateDecoration,
|
||||||
messageType = if (event.root.isSticker()) { MessageType.MSGTYPE_STICKER_LOCAL } else { event.root.getMsgType() }
|
messageType = if (event.root.isSticker()) {
|
||||||
|
MessageType.MSGTYPE_STICKER_LOCAL
|
||||||
|
} else {
|
||||||
|
event.root.getMsgType()
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,7 +234,7 @@ class MessageInformationDataFactory @Inject constructor(
|
|||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL -> true
|
EventType.KEY_VERIFICATION_CANCEL -> true
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
event.getLastMessageContent() is MessageVerificationRequestContent
|
event.getVectorLastMessageContent() is MessageVerificationRequestContent
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.helper
|
package im.vector.app.features.home.room.detail.timeline.helper
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ object TimelineDisplayableEvents {
|
|||||||
/**
|
/**
|
||||||
* All types we have an item to build with. Every type not defined here will be shown as DefaultItem if forced to be shown, otherwise will be hidden.
|
* All types we have an item to build with. Every type not defined here will be shown as DefaultItem if forced to be shown, otherwise will be hidden.
|
||||||
*/
|
*/
|
||||||
val DISPLAYABLE_TYPES = listOf(
|
val DISPLAYABLE_TYPES: List<String> = listOf(
|
||||||
EventType.MESSAGE,
|
EventType.MESSAGE,
|
||||||
EventType.STATE_ROOM_WIDGET_LEGACY,
|
EventType.STATE_ROOM_WIDGET_LEGACY,
|
||||||
EventType.STATE_ROOM_WIDGET,
|
EventType.STATE_ROOM_WIDGET,
|
||||||
@ -51,7 +52,11 @@ object TimelineDisplayableEvents {
|
|||||||
EventType.STATE_ROOM_JOIN_RULES,
|
EventType.STATE_ROOM_JOIN_RULES,
|
||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL,
|
EventType.KEY_VERIFICATION_CANCEL,
|
||||||
) + EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO + EventType.BEACON_LOCATION_DATA
|
STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
) +
|
||||||
|
EventType.POLL_START +
|
||||||
|
EventType.STATE_ROOM_BEACON_INFO +
|
||||||
|
EventType.BEACON_LOCATION_DATA
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
|
fun TimelineEvent.isRoomConfiguration(roomCreatorUserId: String?): Boolean {
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.home.room.detail.timeline.item
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.TextView
|
||||||
|
import com.airbnb.epoxy.EpoxyAttribute
|
||||||
|
import com.airbnb.epoxy.EpoxyModelClass
|
||||||
|
import im.vector.app.R
|
||||||
|
import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
|
||||||
|
@EpoxyModelClass
|
||||||
|
abstract class MessageVoiceBroadcastItem : AbsMessageItem<MessageVoiceBroadcastItem.Holder>() {
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var callback: TimelineEventController.Callback? = null
|
||||||
|
|
||||||
|
@EpoxyAttribute
|
||||||
|
var voiceBroadcastState: VoiceBroadcastState? = null
|
||||||
|
|
||||||
|
override fun bind(holder: Holder) {
|
||||||
|
super.bind(holder)
|
||||||
|
bindVoiceBroadcastItem(holder)
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n") // Temporary text
|
||||||
|
private fun bindVoiceBroadcastItem(holder: Holder) {
|
||||||
|
with(holder) {
|
||||||
|
currentStateText.text = "Voice Broadcast state: ${voiceBroadcastState?.value ?: "None"}"
|
||||||
|
playButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.PAUSED
|
||||||
|
pauseButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED || voiceBroadcastState == VoiceBroadcastState.RESUMED
|
||||||
|
stopButton.isEnabled = voiceBroadcastState == VoiceBroadcastState.STARTED ||
|
||||||
|
voiceBroadcastState == VoiceBroadcastState.RESUMED ||
|
||||||
|
voiceBroadcastState == VoiceBroadcastState.PAUSED
|
||||||
|
playButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Resume) }
|
||||||
|
pauseButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Pause) }
|
||||||
|
stopButton.setOnClickListener { attributes.callback?.onTimelineItemAction(RoomDetailAction.VoiceBroadcastAction.Stop) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getViewStubId() = STUB_ID
|
||||||
|
|
||||||
|
class Holder : AbsMessageLocationItem.Holder(STUB_ID) {
|
||||||
|
val currentStateText by bind<TextView>(R.id.currentStateText)
|
||||||
|
val playButton by bind<ImageButton>(R.id.playButton)
|
||||||
|
val pauseButton by bind<ImageButton>(R.id.pauseButton)
|
||||||
|
val stopButton by bind<ImageButton>(R.id.stopButton)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val STUB_ID = R.id.messageVoiceBroadcastStub
|
||||||
|
}
|
||||||
|
}
|
@ -18,6 +18,7 @@ package im.vector.app.features.home.room.detail.timeline.style
|
|||||||
|
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.extensions.localDateTime
|
import im.vector.app.core.extensions.localDateTime
|
||||||
import im.vector.app.core.resources.LocaleProvider
|
import im.vector.app.core.resources.LocaleProvider
|
||||||
import im.vector.app.core.resources.isRTL
|
import im.vector.app.core.resources.isRTL
|
||||||
@ -29,7 +30,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
|||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageVerificationRequestContent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.isEdition
|
import org.matrix.android.sdk.api.session.room.timeline.isEdition
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
|
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@ -126,7 +126,7 @@ class TimelineMessageLayoutFactory @Inject constructor(
|
|||||||
isLastFromThisSender = isLastFromThisSender
|
isLastFromThisSender = isLastFromThisSender
|
||||||
)
|
)
|
||||||
|
|
||||||
val messageContent = event.getLastMessageContent()
|
val messageContent = event.getVectorLastMessageContent()
|
||||||
TimelineMessageLayout.Bubble(
|
TimelineMessageLayout.Bubble(
|
||||||
showAvatar = showInformation && !isSentByMe,
|
showAvatar = showInformation && !isSentByMe,
|
||||||
showDisplayName = showInformation && !isSentByMe,
|
showDisplayName = showInformation && !isSentByMe,
|
||||||
@ -167,7 +167,7 @@ class TimelineMessageLayoutFactory @Inject constructor(
|
|||||||
private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean {
|
private fun TimelineEvent.shouldBuildBubbleLayout(): Boolean {
|
||||||
val type = root.getClearType()
|
val type = root.getClearType()
|
||||||
if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
|
if (type in EVENT_TYPES_WITH_BUBBLE_LAYOUT) {
|
||||||
val messageContent = getLastMessageContent()
|
val messageContent = getVectorLastMessageContent()
|
||||||
return messageContent?.msgType !in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT
|
return messageContent?.msgType !in MSG_TYPES_WITHOUT_BUBBLE_LAYOUT
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -212,7 +212,7 @@ class TimelineMessageLayoutFactory @Inject constructor(
|
|||||||
EventType.KEY_VERIFICATION_DONE,
|
EventType.KEY_VERIFICATION_DONE,
|
||||||
EventType.KEY_VERIFICATION_CANCEL -> true
|
EventType.KEY_VERIFICATION_CANCEL -> true
|
||||||
EventType.MESSAGE -> {
|
EventType.MESSAGE -> {
|
||||||
event.getLastMessageContent() is MessageVerificationRequestContent
|
event.getVectorLastMessageContent() is MessageVerificationRequestContent
|
||||||
}
|
}
|
||||||
else -> false
|
else -> false
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ package im.vector.app.features.notifications
|
|||||||
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.extensions.takeAs
|
import im.vector.app.core.extensions.takeAs
|
||||||
import im.vector.app.core.resources.BuildMeta
|
import im.vector.app.core.resources.BuildMeta
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
@ -45,7 +46,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
|
|||||||
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
import org.matrix.android.sdk.api.session.room.sender.SenderInfo
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
|
import org.matrix.android.sdk.api.session.room.timeline.getEditedEventId
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@ -231,7 +231,7 @@ class NotifiableEventResolver @Inject constructor(
|
|||||||
|
|
||||||
private suspend fun TimelineEvent.downloadAndExportImage(session: Session): Uri? {
|
private suspend fun TimelineEvent.downloadAndExportImage(session: Session): Uri? {
|
||||||
return kotlin.runCatching {
|
return kotlin.runCatching {
|
||||||
getLastMessageContent()?.takeAs<MessageWithAttachmentContent>()?.let { imageMessage ->
|
getVectorLastMessageContent()?.takeAs<MessageWithAttachmentContent>()?.let { imageMessage ->
|
||||||
val fileService = session.fileService()
|
val fileService = session.fileService()
|
||||||
fileService.downloadFile(imageMessage)
|
fileService.downloadFile(imageMessage)
|
||||||
fileService.getTemporarySharableURI(imageMessage)
|
fileService.getTemporarySharableURI(imageMessage)
|
||||||
|
@ -22,6 +22,7 @@ import dagger.assisted.AssistedFactory
|
|||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
import im.vector.app.core.di.MavericksAssistedViewModelFactory
|
||||||
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
import im.vector.app.core.di.hiltMavericksViewModelFactory
|
||||||
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
import im.vector.app.core.platform.VectorViewModel
|
import im.vector.app.core.platform.VectorViewModel
|
||||||
import im.vector.app.features.poll.PollMode
|
import im.vector.app.features.poll.PollMode
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
@ -29,7 +30,6 @@ 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.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
import org.matrix.android.sdk.api.session.room.model.message.PollType
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
|
|
||||||
|
|
||||||
class CreatePollViewModel @AssistedInject constructor(
|
class CreatePollViewModel @AssistedInject constructor(
|
||||||
@Assisted private val initialState: CreatePollViewState,
|
@Assisted private val initialState: CreatePollViewState,
|
||||||
@ -72,7 +72,7 @@ class CreatePollViewModel @AssistedInject constructor(
|
|||||||
|
|
||||||
private fun initializeEditedPoll(eventId: String) {
|
private fun initializeEditedPoll(eventId: String) {
|
||||||
val event = room.getTimelineEvent(eventId) ?: return
|
val event = room.getTimelineEvent(eventId) ?: return
|
||||||
val content = event.getLastMessageContent() as? MessagePollContent ?: return
|
val content = event.getVectorLastMessageContent() as? MessagePollContent ?: return
|
||||||
|
|
||||||
val pollCreationInfo = content.getBestPollCreationInfo()
|
val pollCreationInfo = content.getBestPollCreationInfo()
|
||||||
val pollType = pollCreationInfo?.kind ?: PollType.DISCLOSED_UNSTABLE
|
val pollType = pollCreationInfo?.kind ?: PollType.DISCLOSED_UNSTABLE
|
||||||
|
@ -0,0 +1,20 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
/** Voice Broadcast State Event. */
|
||||||
|
const val STATE_ROOM_VOICE_BROADCAST_INFO = "io.element.voice_broadcast_info"
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.PauseVoiceBroadcastUseCase
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.ResumeVoiceBroadcastUseCase
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.StartVoiceBroadcastUseCase
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.StopVoiceBroadcastUseCase
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class to record voice broadcast.
|
||||||
|
*/
|
||||||
|
class VoiceBroadcastHelper @Inject constructor(
|
||||||
|
private val startVoiceBroadcastUseCase: StartVoiceBroadcastUseCase,
|
||||||
|
private val pauseVoiceBroadcastUseCase: PauseVoiceBroadcastUseCase,
|
||||||
|
private val resumeVoiceBroadcastUseCase: ResumeVoiceBroadcastUseCase,
|
||||||
|
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
||||||
|
) {
|
||||||
|
suspend fun startVoiceBroadcast(roomId: String) = startVoiceBroadcastUseCase.execute(roomId)
|
||||||
|
|
||||||
|
suspend fun pauseVoiceBroadcast(roomId: String) = pauseVoiceBroadcastUseCase.execute(roomId)
|
||||||
|
|
||||||
|
suspend fun resumeVoiceBroadcast(roomId: String) = resumeVoiceBroadcastUseCase.execute(roomId)
|
||||||
|
|
||||||
|
suspend fun stopVoiceBroadcast(roomId: String) = stopVoiceBroadcastUseCase.execute(roomId)
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.MessageType.MSGTYPE_VOICE_BROADCAST_INFO
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content of the state event of type [STATE_ROOM_VOICE_BROADCAST_INFO].
|
||||||
|
*
|
||||||
|
* It contains general info related to a voice broadcast.
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class MessageVoiceBroadcastInfoContent(
|
||||||
|
/** Local message type, not from server. */
|
||||||
|
@Transient override val msgType: String = MSGTYPE_VOICE_BROADCAST_INFO,
|
||||||
|
@Json(name = "body") override val body: String = "",
|
||||||
|
@Json(name = "m.relates_to") override val relatesTo: RelationDefaultContent? = null,
|
||||||
|
@Json(name = "m.new_content") override val newContent: Content? = null,
|
||||||
|
|
||||||
|
/** The [VoiceBroadcastState] value. **/
|
||||||
|
@Json(name = "state") val voiceBroadcastStateStr: String = "",
|
||||||
|
/** The length of the voice chunks in seconds. **/
|
||||||
|
@Json(name = "chunk_length") val chunkLength: Long? = null,
|
||||||
|
) : MessageContent {
|
||||||
|
|
||||||
|
val voiceBroadcastState: VoiceBroadcastState? = VoiceBroadcastState.values()
|
||||||
|
.find { it.value == voiceBroadcastStateStr }
|
||||||
|
?: run {
|
||||||
|
Timber.w("Invalid value for state: `$voiceBroadcastStateStr`")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
@ -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<MessageVoiceBroadcastInfoContent>()
|
||||||
|
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
|
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 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 im.vector.app.features.voicebroadcast.model
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://github.com/vector-im/element-meta/discussions/632
|
||||||
|
*/
|
||||||
|
@JsonClass(generateAdapter = false)
|
||||||
|
enum class VoiceBroadcastState(val value: String) {
|
||||||
|
/**
|
||||||
|
* The voice broadcast had been started and is currently being live.
|
||||||
|
*/
|
||||||
|
@Json(name = "started") STARTED("started"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The voice broadcast has been paused and may be resumed at any time by the recorder.
|
||||||
|
*/
|
||||||
|
@Json(name = "paused") PAUSED("paused"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The voice broadcast is currently being live again.
|
||||||
|
*/
|
||||||
|
@Json(name = "resumed") RESUMED("resumed"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The voice broadcast has ended.
|
||||||
|
*/
|
||||||
|
@Json(name = "stopped") STOPPED("stopped"),
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class PauseVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
|
Timber.d("## PauseVoiceBroadcastUseCase: Pause voice broadcast requested")
|
||||||
|
|
||||||
|
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.reference)
|
||||||
|
else -> Timber.d("## PauseVoiceBroadcastUseCase: Cannot pause voice broadcast: currentState=$voiceBroadcastState")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun pauseVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||||
|
Timber.d("## PauseVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
|
room.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = session.myUserId,
|
||||||
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
|
relatesTo = reference,
|
||||||
|
voiceBroadcastStateStr = VoiceBroadcastState.PAUSED.value,
|
||||||
|
).toContent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO pause recording audio files
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ResumeVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
|
Timber.d("## ResumeVoiceBroadcastUseCase: Resume voice broadcast requested")
|
||||||
|
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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")
|
||||||
|
room.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = session.myUserId,
|
||||||
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
|
relatesTo = reference,
|
||||||
|
voiceBroadcastStateStr = VoiceBroadcastState.RESUMED.value,
|
||||||
|
).toContent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO resume recording audio files
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
|
Timber.d("## StartVoiceBroadcastUseCase: Start voice broadcast requested")
|
||||||
|
|
||||||
|
val onGoingVoiceBroadcastEvents = room.stateService().getStateEvents(
|
||||||
|
setOf(STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||||
|
QueryStringValue.IsNotEmpty
|
||||||
|
)
|
||||||
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
|
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
||||||
|
|
||||||
|
if (onGoingVoiceBroadcastEvents.isEmpty()) {
|
||||||
|
startVoiceBroadcast(room)
|
||||||
|
} else {
|
||||||
|
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: currentVoiceBroadcastEvents=$onGoingVoiceBroadcastEvents")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun startVoiceBroadcast(room: Room) {
|
||||||
|
Timber.d("## StartVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
|
room.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = session.myUserId,
|
||||||
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
|
voiceBroadcastStateStr = VoiceBroadcastState.STARTED.value,
|
||||||
|
chunkLength = 5L, // TODO Get length from voice broadcast settings
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO start recording audio files
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.Room
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.RelationDefaultContent
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class StopVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
private val session: Session,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend fun execute(roomId: String): Result<Unit> = runCatching {
|
||||||
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
|
Timber.d("## StopVoiceBroadcastUseCase: Stop voice broadcast requested")
|
||||||
|
|
||||||
|
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.reference)
|
||||||
|
else -> Timber.d("## StopVoiceBroadcastUseCase: Cannot stop voice broadcast: currentState=$voiceBroadcastState")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun stopVoiceBroadcast(room: Room, reference: RelationDefaultContent?) {
|
||||||
|
Timber.d("## StopVoiceBroadcastUseCase: Send new voice broadcast info state event")
|
||||||
|
room.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = session.myUserId,
|
||||||
|
body = MessageVoiceBroadcastInfoContent(
|
||||||
|
relatesTo = reference,
|
||||||
|
voiceBroadcastStateStr = VoiceBroadcastState.STOPPED.value,
|
||||||
|
).toContent(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO stop recording audio files
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,13 @@
|
|||||||
android:layout="@layout/item_timeline_event_audio_stub"
|
android:layout="@layout/item_timeline_event_audio_stub"
|
||||||
tools:visibility="gone" />
|
tools:visibility="gone" />
|
||||||
|
|
||||||
|
<ViewStub
|
||||||
|
android:id="@+id/messageVoiceBroadcastStub"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout="@layout/item_timeline_event_voice_broadcast_stub"
|
||||||
|
tools:visibility="gone" />
|
||||||
|
|
||||||
<ViewStub
|
<ViewStub
|
||||||
android:id="@+id/messageContentPollStub"
|
android:id="@+id/messageContentPollStub"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/messageRootLayout"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:padding="@dimen/layout_vertical_margin"
|
||||||
|
tools:viewBindingIgnore="true">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/currentStateText"
|
||||||
|
style="@style/Widget.Vector.TextView.HeadlineMedium"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/playButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:text="Voice Broadcast state: STARTED" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/playButton"
|
||||||
|
android:layout_width="@dimen/item_event_message_media_button_size"
|
||||||
|
android:layout_height="@dimen/item_event_message_media_button_size"
|
||||||
|
android:layout_marginTop="@dimen/layout_vertical_margin"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:contentDescription="@string/a11y_play_voice_message"
|
||||||
|
android:src="@drawable/ic_play_pause_play"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/pauseButton"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/currentStateText"
|
||||||
|
app:tint="@color/vector_content_primary_tint_selector" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/pauseButton"
|
||||||
|
android:layout_width="@dimen/item_event_message_media_button_size"
|
||||||
|
android:layout_height="@dimen/item_event_message_media_button_size"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:contentDescription="@string/a11y_pause_voice_message"
|
||||||
|
android:src="@drawable/ic_play_pause_pause"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/playButton"
|
||||||
|
app:layout_constraintEnd_toStartOf="@id/stopButton"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/playButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/playButton"
|
||||||
|
app:tint="@color/vector_content_primary_tint_selector" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/stopButton"
|
||||||
|
android:layout_width="@dimen/item_event_message_media_button_size"
|
||||||
|
android:layout_height="@dimen/item_event_message_media_button_size"
|
||||||
|
android:background="?android:attr/selectableItemBackground"
|
||||||
|
android:contentDescription="@string/a11y_stop_voice_message"
|
||||||
|
android:src="@drawable/ic_close_24dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/pauseButton"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@id/pauseButton"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/playButton"
|
||||||
|
app:tint="@color/vector_content_primary_tint_selector" />
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
package im.vector.app.features.home.room.detail.timeline.action
|
package im.vector.app.features.home.room.detail.timeline.action
|
||||||
|
|
||||||
|
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
|
||||||
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
import im.vector.app.test.fakes.FakeActiveSessionHolder
|
||||||
import io.mockk.mockk
|
import io.mockk.mockk
|
||||||
import org.amshove.kluent.shouldBe
|
import org.amshove.kluent.shouldBe
|
||||||
@ -34,7 +35,7 @@ class CheckIfCanRedactEventUseCaseTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `given an event which can be redacted and owned by user when use case executes then the result is true`() {
|
fun `given an event which can be redacted and owned by user when use case executes then the result is true`() {
|
||||||
val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) +
|
val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER, STATE_ROOM_VOICE_BROADCAST_INFO) +
|
||||||
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
|
||||||
|
|
||||||
canRedactEventTypes.forEach { eventType ->
|
canRedactEventTypes.forEach { eventType ->
|
||||||
|
@ -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()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.test.fakes.FakeRoom
|
||||||
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import io.mockk.clearAllMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.slot
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
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.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room_id"
|
||||||
|
private const val AN_EVENT_ID = "event_id"
|
||||||
|
private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id"
|
||||||
|
|
||||||
|
class PauseVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoom = FakeRoom()
|
||||||
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
|
private val pauseVoiceBroadcastUseCase = PauseVoiceBroadcastUseCase(fakeSession)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is paused or not`() = runTest {
|
||||||
|
val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map {
|
||||||
|
when (it) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
VoiceBroadcastState.RESUMED -> Case(it, true)
|
||||||
|
VoiceBroadcastState.STOPPED,
|
||||||
|
VoiceBroadcastState.PAUSED,
|
||||||
|
null -> Case(it, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases.forEach { case ->
|
||||||
|
if (case.canPauseVoiceBroadcast) {
|
||||||
|
testVoiceBroadcastPaused(case.previousState)
|
||||||
|
} else {
|
||||||
|
testVoiceBroadcastNotPaused(case.previousState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastPaused(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
val voiceBroadcastInfoContentInterceptor = slot<Content>()
|
||||||
|
coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID }
|
||||||
|
|
||||||
|
// When
|
||||||
|
pauseVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify {
|
||||||
|
fakeRoom.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
body = any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.PAUSED
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastNotPaused(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
|
||||||
|
// When
|
||||||
|
pauseVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) {
|
||||||
|
val relatesTo = when (state) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
null -> null
|
||||||
|
VoiceBroadcastState.PAUSED,
|
||||||
|
VoiceBroadcastState.RESUMED,
|
||||||
|
VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID)
|
||||||
|
}
|
||||||
|
val event = state?.let {
|
||||||
|
Event(
|
||||||
|
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
||||||
|
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
|
voiceBroadcastStateStr = state.value,
|
||||||
|
relatesTo = relatesTo
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fakeRoom.stateService().givenGetStateEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Case(val previousState: VoiceBroadcastState?, val canPauseVoiceBroadcast: Boolean)
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.test.fakes.FakeRoom
|
||||||
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import io.mockk.clearAllMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.slot
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
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.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room_id"
|
||||||
|
private const val AN_EVENT_ID = "event_id"
|
||||||
|
private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id"
|
||||||
|
|
||||||
|
class ResumeVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoom = FakeRoom()
|
||||||
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
|
private val resumeVoiceBroadcastUseCase = ResumeVoiceBroadcastUseCase(fakeSession)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is resumed or not`() = runTest {
|
||||||
|
val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map {
|
||||||
|
when (it) {
|
||||||
|
VoiceBroadcastState.PAUSED -> Case(it, true)
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
VoiceBroadcastState.RESUMED,
|
||||||
|
VoiceBroadcastState.STOPPED,
|
||||||
|
null -> Case(it, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases.forEach { case ->
|
||||||
|
if (case.canResumeVoiceBroadcast) {
|
||||||
|
testVoiceBroadcastResumed(case.previousState)
|
||||||
|
} else {
|
||||||
|
testVoiceBroadcastNotResumed(case.previousState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastResumed(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
val voiceBroadcastInfoContentInterceptor = slot<Content>()
|
||||||
|
coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID }
|
||||||
|
|
||||||
|
// When
|
||||||
|
resumeVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify {
|
||||||
|
fakeRoom.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
body = any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.RESUMED
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastNotResumed(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
|
||||||
|
// When
|
||||||
|
resumeVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) {
|
||||||
|
val relatesTo = when (state) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
null -> null
|
||||||
|
VoiceBroadcastState.PAUSED,
|
||||||
|
VoiceBroadcastState.RESUMED,
|
||||||
|
VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID)
|
||||||
|
}
|
||||||
|
val event = state?.let {
|
||||||
|
Event(
|
||||||
|
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
||||||
|
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
|
voiceBroadcastStateStr = state.value,
|
||||||
|
relatesTo = relatesTo
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fakeRoom.stateService().givenGetStateEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Case(val previousState: VoiceBroadcastState?, val canResumeVoiceBroadcast: Boolean)
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.test.fakes.FakeRoom
|
||||||
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import io.mockk.clearAllMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.slot
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.amshove.kluent.shouldBeNull
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room_id"
|
||||||
|
private const val AN_EVENT_ID = "event_id"
|
||||||
|
private const val A_USER_ID = "user_id"
|
||||||
|
|
||||||
|
class StartVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoom = FakeRoom()
|
||||||
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
|
private val startVoiceBroadcastUseCase = StartVoiceBroadcastUseCase(fakeSession)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room id with potential several existing voice broadcast states when calling execute then the voice broadcast is started or not`() = runTest {
|
||||||
|
val cases = VoiceBroadcastState.values()
|
||||||
|
.flatMap { first ->
|
||||||
|
VoiceBroadcastState.values().map { second ->
|
||||||
|
Case(
|
||||||
|
voiceBroadcasts = listOf(VoiceBroadcast(fakeSession.myUserId, first), VoiceBroadcast(A_USER_ID, second)),
|
||||||
|
canStartVoiceBroadcast = first == VoiceBroadcastState.STOPPED && second == VoiceBroadcastState.STOPPED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.plus(Case(emptyList(), true))
|
||||||
|
|
||||||
|
cases.forEach { case ->
|
||||||
|
if (case.canStartVoiceBroadcast) {
|
||||||
|
testVoiceBroadcastStarted(case.voiceBroadcasts)
|
||||||
|
} else {
|
||||||
|
testVoiceBroadcastNotStarted(case.voiceBroadcasts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastStarted(voiceBroadcasts: List<VoiceBroadcast>) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcasts(voiceBroadcasts)
|
||||||
|
val voiceBroadcastInfoContentInterceptor = slot<Content>()
|
||||||
|
coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID }
|
||||||
|
|
||||||
|
// When
|
||||||
|
startVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify {
|
||||||
|
fakeRoom.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
body = any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.STARTED
|
||||||
|
voiceBroadcastInfoContent?.relatesTo.shouldBeNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastNotStarted(voiceBroadcasts: List<VoiceBroadcast>) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcasts(voiceBroadcasts)
|
||||||
|
|
||||||
|
// When
|
||||||
|
startVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcasts(voiceBroadcasts: List<VoiceBroadcast>) {
|
||||||
|
val events = voiceBroadcasts.map {
|
||||||
|
Event(
|
||||||
|
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = it.userId,
|
||||||
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
|
voiceBroadcastStateStr = it.state.value
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fakeRoom.stateService().givenGetStateEvents(QueryStringValue.IsNotEmpty, events)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)
|
||||||
|
private data class Case(val voiceBroadcasts: List<VoiceBroadcast>, val canStartVoiceBroadcast: Boolean)
|
||||||
|
}
|
@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* 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.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.test.fakes.FakeRoom
|
||||||
|
import im.vector.app.test.fakes.FakeRoomService
|
||||||
|
import im.vector.app.test.fakes.FakeSession
|
||||||
|
import io.mockk.clearAllMocks
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.coVerify
|
||||||
|
import io.mockk.slot
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.amshove.kluent.shouldBe
|
||||||
|
import org.junit.Test
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Content
|
||||||
|
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.model.relation.RelationDefaultContent
|
||||||
|
|
||||||
|
private const val A_ROOM_ID = "room_id"
|
||||||
|
private const val AN_EVENT_ID = "event_id"
|
||||||
|
private const val A_STARTED_VOICE_BROADCAST_EVENT_ID = "a_started_voice_broadcast_event_id"
|
||||||
|
|
||||||
|
class StopVoiceBroadcastUseCaseTest {
|
||||||
|
|
||||||
|
private val fakeRoom = FakeRoom()
|
||||||
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
|
private val stopVoiceBroadcastUseCase = StopVoiceBroadcastUseCase(fakeSession)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `given a room id with a potential existing voice broadcast state when calling execute then the voice broadcast is stopped or not`() = runTest {
|
||||||
|
val cases = listOf<VoiceBroadcastState?>(null).plus(VoiceBroadcastState.values()).map {
|
||||||
|
when (it) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
VoiceBroadcastState.RESUMED,
|
||||||
|
VoiceBroadcastState.PAUSED -> Case(it, true)
|
||||||
|
VoiceBroadcastState.STOPPED,
|
||||||
|
null -> Case(it, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cases.forEach { case ->
|
||||||
|
if (case.canStopVoiceBroadcast) {
|
||||||
|
testVoiceBroadcastStopped(case.previousState)
|
||||||
|
} else {
|
||||||
|
testVoiceBroadcastNotStopped(case.previousState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastStopped(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
val voiceBroadcastInfoContentInterceptor = slot<Content>()
|
||||||
|
coEvery { fakeRoom.stateService().sendStateEvent(any(), any(), capture(voiceBroadcastInfoContentInterceptor)) } coAnswers { AN_EVENT_ID }
|
||||||
|
|
||||||
|
// When
|
||||||
|
stopVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify {
|
||||||
|
fakeRoom.stateService().sendStateEvent(
|
||||||
|
eventType = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
body = any(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val voiceBroadcastInfoContent = voiceBroadcastInfoContentInterceptor.captured.toModel<MessageVoiceBroadcastInfoContent>()
|
||||||
|
voiceBroadcastInfoContent?.voiceBroadcastState shouldBe VoiceBroadcastState.STOPPED
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.type shouldBe RelationType.REFERENCE
|
||||||
|
voiceBroadcastInfoContent?.relatesTo?.eventId shouldBe A_STARTED_VOICE_BROADCAST_EVENT_ID
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun testVoiceBroadcastNotStopped(previousState: VoiceBroadcastState?) {
|
||||||
|
// Given
|
||||||
|
clearAllMocks()
|
||||||
|
givenAVoiceBroadcastState(previousState)
|
||||||
|
|
||||||
|
// When
|
||||||
|
stopVoiceBroadcastUseCase.execute(A_ROOM_ID)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
coVerify(exactly = 0) { fakeRoom.stateService().sendStateEvent(any(), any(), any()) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun givenAVoiceBroadcastState(state: VoiceBroadcastState?) {
|
||||||
|
val relatesTo = when (state) {
|
||||||
|
VoiceBroadcastState.STARTED,
|
||||||
|
null -> null
|
||||||
|
VoiceBroadcastState.PAUSED,
|
||||||
|
VoiceBroadcastState.RESUMED,
|
||||||
|
VoiceBroadcastState.STOPPED -> RelationDefaultContent(RelationType.REFERENCE, A_STARTED_VOICE_BROADCAST_EVENT_ID)
|
||||||
|
}
|
||||||
|
val event = state?.let {
|
||||||
|
Event(
|
||||||
|
eventId = if (state == VoiceBroadcastState.STARTED) A_STARTED_VOICE_BROADCAST_EVENT_ID else AN_EVENT_ID,
|
||||||
|
type = STATE_ROOM_VOICE_BROADCAST_INFO,
|
||||||
|
stateKey = fakeSession.myUserId,
|
||||||
|
content = MessageVoiceBroadcastInfoContent(
|
||||||
|
voiceBroadcastStateStr = state.value,
|
||||||
|
relatesTo = relatesTo
|
||||||
|
).toContent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
fakeRoom.stateService().givenGetStateEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Case(val previousState: VoiceBroadcastState?, val canStopVoiceBroadcast: Boolean)
|
||||||
|
}
|
@ -24,6 +24,7 @@ class FakeRoom(
|
|||||||
private val fakeSendService: FakeSendService = FakeSendService(),
|
private val fakeSendService: FakeSendService = FakeSendService(),
|
||||||
private val fakeTimelineService: FakeTimelineService = FakeTimelineService(),
|
private val fakeTimelineService: FakeTimelineService = FakeTimelineService(),
|
||||||
private val fakeRelationService: FakeRelationService = FakeRelationService(),
|
private val fakeRelationService: FakeRelationService = FakeRelationService(),
|
||||||
|
private val fakeStateService: FakeStateService = FakeStateService(),
|
||||||
) : Room by mockk() {
|
) : Room by mockk() {
|
||||||
|
|
||||||
override fun locationSharingService() = fakeLocationSharingService
|
override fun locationSharingService() = fakeLocationSharingService
|
||||||
@ -33,4 +34,6 @@ class FakeRoom(
|
|||||||
override fun timelineService() = fakeTimelineService
|
override fun timelineService() = fakeTimelineService
|
||||||
|
|
||||||
override fun relationService() = fakeRelationService
|
override fun relationService() = fakeRelationService
|
||||||
|
|
||||||
|
override fun stateService() = fakeStateService
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 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.test.fakes
|
||||||
|
|
||||||
|
import io.mockk.every
|
||||||
|
import io.mockk.mockk
|
||||||
|
import org.matrix.android.sdk.api.query.QueryStateEventValue
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.room.state.StateService
|
||||||
|
|
||||||
|
class FakeStateService : StateService by mockk(relaxed = true) {
|
||||||
|
|
||||||
|
fun givenGetStateEvents(stateKey: QueryStateEventValue, result: List<Event>) {
|
||||||
|
every { getStateEvents(any(), stateKey) } returns result
|
||||||
|
}
|
||||||
|
|
||||||
|
fun givenGetStateEvent(event: Event?) {
|
||||||
|
every { getStateEvent(any(), any()) } returns event
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user