Skip to content

Commit 48d2cc4

Browse files
authored
Merge pull request #7273 from vector-im/feature/fre/voice_broadcast_state_event
Voice Broadcast - Send state events
2 parents bd36831 + b286a52 commit 48d2cc4

37 files changed

+1396
-33
lines changed

changelog.d/7273.wip

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[Voice Broadcast] Add the "io.element.voice_broadcast_info" state event with a minimalist timeline widget

matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/model/message/MessageType.kt

+3
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,7 @@ object MessageType {
4343
// Fake message types for live location events to be able to inherit them from MessageContent
4444
const val MSGTYPE_BEACON_INFO = "org.matrix.android.sdk.beacon.info"
4545
const val MSGTYPE_BEACON_LOCATION_DATA = "org.matrix.android.sdk.beacon.location.data"
46+
47+
// Fake message types for voice broadcast events to be able to inherit them from MessageContent
48+
const val MSGTYPE_VOICE_BROADCAST_INFO = "io.element.voicebroadcast.info"
4649
}

vector/src/main/java/im/vector/app/core/extensions/TimelineEvent.kt

+17
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,30 @@
1616

1717
package im.vector.app.core.extensions
1818

19+
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
20+
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
1921
import org.matrix.android.sdk.api.session.events.model.EventType
22+
import org.matrix.android.sdk.api.session.events.model.toModel
23+
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
2024
import org.matrix.android.sdk.api.session.room.send.SendState
2125
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
26+
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
2227

2328
fun TimelineEvent.canReact(): Boolean {
2429
// Only event of type EventType.MESSAGE, EventType.STICKER and EventType.POLL_START are supported for the moment
2530
return root.getClearType() in listOf(EventType.MESSAGE, EventType.STICKER) + EventType.POLL_START &&
2631
root.sendState == SendState.SYNCED &&
2732
!root.isRedacted()
2833
}
34+
35+
/**
36+
* Get last MessageContent, after a possible edition.
37+
* This method iterate on the vector event types and fallback to [getLastMessageContent] from the matrix sdk for the other types.
38+
*/
39+
fun TimelineEvent.getVectorLastMessageContent(): MessageContent? {
40+
// Iterate on event types which are not part of the matrix sdk, otherwise fallback to the sdk method
41+
return when (root.getClearType()) {
42+
STATE_ROOM_VOICE_BROADCAST_INFO -> (annotations?.editSummary?.latestContent ?: root.getClearContent()).toModel<MessageVoiceBroadcastInfoContent>()
43+
else -> getLastMessageContent()
44+
}
45+
}

vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailAction.kt

+7-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
7979
data class ReRequestKeys(val eventId: String) : RoomDetailAction()
8080

8181
object SelectStickerAttachment : RoomDetailAction()
82-
object StartVoiceBroadcast : RoomDetailAction()
8382
object OpenIntegrationManager : RoomDetailAction()
8483
object ManageIntegrations : RoomDetailAction()
8584
data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
@@ -120,4 +119,11 @@ sealed class RoomDetailAction : VectorViewModelAction {
120119
object StopLiveLocationSharing : RoomDetailAction()
121120

122121
object OpenElementCallWidget : RoomDetailAction()
122+
123+
sealed class VoiceBroadcastAction : RoomDetailAction() {
124+
object Start : VoiceBroadcastAction()
125+
object Pause : VoiceBroadcastAction()
126+
object Resume : VoiceBroadcastAction()
127+
object Stop : VoiceBroadcastAction()
128+
}
123129
}

vector/src/main/java/im/vector/app/features/home/room/detail/TimelineViewModel.kt

+13-4
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ import im.vector.app.features.raw.wellknown.withElementWellKnown
6565
import im.vector.app.features.session.coroutineScope
6666
import im.vector.app.features.settings.VectorDataStore
6767
import im.vector.app.features.settings.VectorPreferences
68+
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
6869
import im.vector.lib.core.utils.flow.chunk
6970
import kotlinx.coroutines.Dispatchers
7071
import kotlinx.coroutines.flow.MutableSharedFlow
@@ -149,6 +150,7 @@ class TimelineViewModel @AssistedInject constructor(
149150
buildMeta: BuildMeta,
150151
timelineFactory: TimelineFactory,
151152
private val spaceStateHandler: SpaceStateHandler,
153+
private val voiceBroadcastHelper: VoiceBroadcastHelper,
152154
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState),
153155
Timeline.Listener, ChatEffectManager.Delegate, CallProtocolsChecker.Listener, LocationSharingServiceConnection.Callback {
154156

@@ -456,7 +458,7 @@ class TimelineViewModel @AssistedInject constructor(
456458
is RoomDetailAction.ReRequestKeys -> handleReRequestKeys(action)
457459
is RoomDetailAction.TapOnFailedToDecrypt -> handleTapOnFailedToDecrypt(action)
458460
is RoomDetailAction.SelectStickerAttachment -> handleSelectStickerAttachment()
459-
is RoomDetailAction.StartVoiceBroadcast -> handleStartVoiceBroadcast()
461+
is RoomDetailAction.VoiceBroadcastAction -> handleVoiceBroadcastAction(action)
460462
is RoomDetailAction.OpenIntegrationManager -> handleOpenIntegrationManager()
461463
is RoomDetailAction.StartCall -> handleStartCall(action)
462464
is RoomDetailAction.AcceptCall -> handleAcceptCall(action)
@@ -598,9 +600,16 @@ class TimelineViewModel @AssistedInject constructor(
598600
}
599601
}
600602

601-
private fun handleStartVoiceBroadcast() {
602-
// Todo implement start voice broadcast action
603-
Timber.d("Start voice broadcast clicked")
603+
private fun handleVoiceBroadcastAction(action: RoomDetailAction.VoiceBroadcastAction) {
604+
if (room == null) return
605+
viewModelScope.launch {
606+
when (action) {
607+
RoomDetailAction.VoiceBroadcastAction.Start -> voiceBroadcastHelper.startVoiceBroadcast(room.roomId)
608+
RoomDetailAction.VoiceBroadcastAction.Pause -> voiceBroadcastHelper.pauseVoiceBroadcast(room.roomId)
609+
RoomDetailAction.VoiceBroadcastAction.Resume -> voiceBroadcastHelper.resumeVoiceBroadcast(room.roomId)
610+
RoomDetailAction.VoiceBroadcastAction.Stop -> voiceBroadcastHelper.stopVoiceBroadcast(room.roomId)
611+
}
612+
}
604613
}
605614

606615
private fun handleOpenIntegrationManager() {

vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerFragment.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import com.vanniktech.emoji.EmojiPopup
4848
import dagger.hilt.android.AndroidEntryPoint
4949
import im.vector.app.R
5050
import im.vector.app.core.error.fatalError
51+
import im.vector.app.core.extensions.getVectorLastMessageContent
5152
import im.vector.app.core.extensions.registerStartForActivityResult
5253
import im.vector.app.core.extensions.showKeyboard
5354
import im.vector.app.core.glide.GlideApp
@@ -73,6 +74,7 @@ import im.vector.app.features.command.ParsedCommand
7374
import im.vector.app.features.home.AvatarRenderer
7475
import im.vector.app.features.home.room.detail.AutoCompleter
7576
import im.vector.app.features.home.room.detail.RoomDetailAction
77+
import im.vector.app.features.home.room.detail.RoomDetailAction.VoiceBroadcastAction
7678
import im.vector.app.features.home.room.detail.TimelineViewModel
7779
import im.vector.app.features.home.room.detail.composer.voice.VoiceMessageRecorderView
7880
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
102104
import org.matrix.android.sdk.api.session.room.model.message.MessagePollContent
103105
import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
104106
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
105-
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
106107
import org.matrix.android.sdk.api.util.MatrixItem
107108
import org.matrix.android.sdk.api.util.toMatrixItem
108109
import reactivecircus.flowbinding.android.view.focusChanges
@@ -355,7 +356,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
355356
setTextColor(matrixItemColorProvider.getColor(MatrixItem.UserItem(event.root.senderId ?: "@")))
356357
}
357358

358-
val messageContent: MessageContent? = event.getLastMessageContent()
359+
val messageContent: MessageContent? = event.getVectorLastMessageContent()
359360
val nonFormattedBody = when (messageContent) {
360361
is MessageAudioContent -> getAudioContentBodyText(messageContent)
361362
is MessagePollContent -> messageContent.getBestPollCreationInfo()?.question?.getBestQuestion()
@@ -653,7 +654,7 @@ class MessageComposerFragment : VectorBaseFragment<FragmentComposerBinding>(), A
653654
locationOwnerId = session.myUserId
654655
)
655656
}
656-
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(RoomDetailAction.StartVoiceBroadcast)
657+
AttachmentTypeSelectorView.Type.VOICE_BROADCAST -> timelineViewModel.handle(VoiceBroadcastAction.Start)
657658
}
658659
}
659660

vector/src/main/java/im/vector/app/features/home/room/detail/composer/MessageComposerViewModel.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import dagger.assisted.AssistedInject
2323
import im.vector.app.R
2424
import im.vector.app.core.di.MavericksAssistedViewModelFactory
2525
import im.vector.app.core.di.hiltMavericksViewModelFactory
26+
import im.vector.app.core.extensions.getVectorLastMessageContent
2627
import im.vector.app.core.platform.VectorViewModel
2728
import im.vector.app.core.resources.StringProvider
2829
import im.vector.app.features.analytics.AnalyticsTracker
@@ -62,7 +63,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageType
6263
import org.matrix.android.sdk.api.session.room.model.relation.shouldRenderInThread
6364
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
6465
import org.matrix.android.sdk.api.session.room.send.UserDraft
65-
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
6666
import org.matrix.android.sdk.api.session.room.timeline.getRelationContent
6767
import org.matrix.android.sdk.api.session.room.timeline.getTextEditableContent
6868
import org.matrix.android.sdk.api.session.space.CreateSpaceParams
@@ -513,7 +513,7 @@ class MessageComposerViewModel @AssistedInject constructor(
513513
room.relationService().editReply(state.sendMode.timelineEvent, it, action.text.toString())
514514
}
515515
} else {
516-
val messageContent = state.sendMode.timelineEvent.getLastMessageContent()
516+
val messageContent = state.sendMode.timelineEvent.getVectorLastMessageContent()
517517
val existingBody = messageContent?.body ?: ""
518518
if (existingBody != action.text) {
519519
room.relationService().editTextMessage(

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/CheckIfCanRedactEventUseCase.kt

+8-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package im.vector.app.features.home.room.detail.timeline.action
1818

1919
import im.vector.app.core.di.ActiveSessionHolder
20+
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
2021
import org.matrix.android.sdk.api.session.events.model.EventType
2122
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
2223
import javax.inject.Inject
@@ -27,8 +28,13 @@ class CheckIfCanRedactEventUseCase @Inject constructor(
2728

2829
fun execute(event: TimelineEvent, actionPermissions: ActionPermissions): Boolean {
2930
// Only some event types are supported for the moment
30-
val canRedactEventTypes = listOf(EventType.MESSAGE, EventType.STICKER) +
31-
EventType.POLL_START + EventType.STATE_ROOM_BEACON_INFO
31+
val canRedactEventTypes: List<String> = listOf(
32+
EventType.MESSAGE,
33+
EventType.STICKER,
34+
STATE_ROOM_VOICE_BROADCAST_INFO,
35+
) +
36+
EventType.POLL_START +
37+
EventType.STATE_ROOM_BEACON_INFO
3238

3339
return event.root.getClearType() in canRedactEventTypes &&
3440
// Message sent by the current user can always be redacted, else check permission for messages sent by other users

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/action/MessageActionsViewModel.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import im.vector.app.core.di.MavericksAssistedViewModelFactory
2525
import im.vector.app.core.di.hiltMavericksViewModelFactory
2626
import im.vector.app.core.error.ErrorFormatter
2727
import im.vector.app.core.extensions.canReact
28+
import im.vector.app.core.extensions.getVectorLastMessageContent
2829
import im.vector.app.core.platform.EmptyViewEvents
2930
import im.vector.app.core.platform.VectorViewModel
3031
import im.vector.app.core.resources.StringProvider
@@ -60,7 +61,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
6061
import org.matrix.android.sdk.api.session.room.powerlevels.PowerLevelsHelper
6162
import org.matrix.android.sdk.api.session.room.send.SendState
6263
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
63-
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
6464
import org.matrix.android.sdk.api.session.room.timeline.hasBeenEdited
6565
import org.matrix.android.sdk.api.session.room.timeline.isPoll
6666
import org.matrix.android.sdk.api.session.room.timeline.isRootThread
@@ -187,7 +187,7 @@ class MessageActionsViewModel @AssistedInject constructor(
187187
when (timelineEvent.root.getClearType()) {
188188
EventType.MESSAGE,
189189
EventType.STICKER -> {
190-
val messageContent: MessageContent? = timelineEvent.getLastMessageContent()
190+
val messageContent: MessageContent? = timelineEvent.getVectorLastMessageContent()
191191
if (messageContent is MessageTextContent && messageContent.format == MessageFormat.FORMAT_MATRIX_HTML) {
192192
val html = messageContent.formattedBody
193193
?.takeIf { it.isNotBlank() }
@@ -253,7 +253,7 @@ class MessageActionsViewModel @AssistedInject constructor(
253253
}
254254

255255
private fun actionsForEvent(timelineEvent: TimelineEvent, actionPermissions: ActionPermissions): List<EventSharedAction> {
256-
val messageContent = timelineEvent.getLastMessageContent()
256+
val messageContent = timelineEvent.getVectorLastMessageContent()
257257
val msgType = messageContent?.msgType
258258

259259
return arrayListOf<EventSharedAction>().apply {

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/MessageItemFactory.kt

+20-2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import dagger.Lazy
2828
import im.vector.app.R
2929
import im.vector.app.core.epoxy.ClickListener
3030
import im.vector.app.core.epoxy.VectorEpoxyModel
31+
import im.vector.app.core.extensions.getVectorLastMessageContent
3132
import im.vector.app.core.files.LocalFilesHelper
3233
import im.vector.app.core.resources.ColorProvider
3334
import im.vector.app.core.resources.StringProvider
@@ -55,6 +56,8 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem
5556
import im.vector.app.features.home.room.detail.timeline.item.MessageLocationItem_
5657
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem
5758
import im.vector.app.features.home.room.detail.timeline.item.MessageTextItem_
59+
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem
60+
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceBroadcastItem_
5861
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem
5962
import im.vector.app.features.home.room.detail.timeline.item.MessageVoiceItem_
6063
import im.vector.app.features.home.room.detail.timeline.item.PollItem
@@ -77,6 +80,7 @@ import im.vector.app.features.media.ImageContentRenderer
7780
import im.vector.app.features.media.VideoContentRenderer
7881
import im.vector.app.features.settings.VectorPreferences
7982
import im.vector.app.features.voice.AudioWaveformView
83+
import im.vector.app.features.voicebroadcast.model.MessageVoiceBroadcastInfoContent
8084
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
8185
import me.gujun.android.span.span
8286
import org.matrix.android.sdk.api.MatrixUrls.isMxcUrl
@@ -102,7 +106,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageVerification
102106
import org.matrix.android.sdk.api.session.room.model.message.MessageVideoContent
103107
import org.matrix.android.sdk.api.session.room.model.message.getFileUrl
104108
import org.matrix.android.sdk.api.session.room.model.message.getThumbnailUrl
105-
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
106109
import org.matrix.android.sdk.api.settings.LightweightSettingsStorage
107110
import org.matrix.android.sdk.api.util.MimeTypes
108111
import javax.inject.Inject
@@ -163,7 +166,7 @@ class MessageItemFactory @Inject constructor(
163166
return buildRedactedItem(attributes, highlight)
164167
}
165168

166-
val messageContent = event.getLastMessageContent()
169+
val messageContent = event.getVectorLastMessageContent()
167170
if (messageContent == null) {
168171
val malformedText = stringProvider.getString(R.string.malformed_message)
169172
return defaultItemFactory.create(malformedText, informationData, highlight, callback)
@@ -197,6 +200,7 @@ class MessageItemFactory @Inject constructor(
197200
is MessagePollContent -> buildPollItem(messageContent, informationData, highlight, callback, attributes)
198201
is MessageLocationContent -> buildLocationItem(messageContent, informationData, highlight, attributes)
199202
is MessageBeaconInfoContent -> liveLocationShareMessageItemFactory.create(params.event, highlight, attributes)
203+
is MessageVoiceBroadcastInfoContent -> buildVoiceBroadcastItem(messageContent, highlight, callback, attributes)
200204
else -> buildNotHandledMessageItem(messageContent, informationData, highlight, callback, attributes)
201205
}
202206
return messageItem?.apply {
@@ -706,6 +710,20 @@ class MessageItemFactory @Inject constructor(
706710
.highlighted(highlight)
707711
}
708712

713+
private fun buildVoiceBroadcastItem(
714+
messageContent: MessageVoiceBroadcastInfoContent,
715+
highlight: Boolean,
716+
callback: TimelineEventController.Callback?,
717+
attributes: AbsMessageItem.Attributes,
718+
): MessageVoiceBroadcastItem? {
719+
return MessageVoiceBroadcastItem_()
720+
.attributes(attributes)
721+
.highlighted(highlight)
722+
.voiceBroadcastState(messageContent.voiceBroadcastState)
723+
.leftGuideline(avatarSizeProvider.leftGuideline)
724+
.callback(callback)
725+
}
726+
709727
private fun List<Int?>?.toFft(): List<Int>? {
710728
return this
711729
?.filterNotNull()

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/factory/TimelineItemFactory.kt

+2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import im.vector.app.core.epoxy.TimelineEmptyItem_
2121
import im.vector.app.core.epoxy.VectorEpoxyModel
2222
import im.vector.app.features.analytics.DecryptionFailureTracker
2323
import im.vector.app.features.home.room.detail.timeline.helper.TimelineEventVisibilityHelper
24+
import im.vector.app.features.voicebroadcast.STATE_ROOM_VOICE_BROADCAST_INFO
2425
import org.matrix.android.sdk.api.session.events.model.EventType
2526
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
2627
import timber.log.Timber
@@ -88,6 +89,7 @@ class TimelineItemFactory @Inject constructor(
8889
// State room create
8990
EventType.STATE_ROOM_CREATE -> roomCreateItemFactory.create(params)
9091
in EventType.STATE_ROOM_BEACON_INFO -> messageItemFactory.create(params)
92+
STATE_ROOM_VOICE_BROADCAST_INFO -> messageItemFactory.create(params)
9193
// Unhandled state event types
9294
else -> {
9395
// Should only happen when shouldShowHiddenEvents() settings is ON

vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package im.vector.app.features.home.room.detail.timeline.format
1919
import dagger.Lazy
2020
import im.vector.app.EmojiSpanify
2121
import im.vector.app.R
22+
import im.vector.app.core.extensions.getVectorLastMessageContent
2223
import im.vector.app.core.resources.ColorProvider
2324
import im.vector.app.core.resources.StringProvider
2425
import im.vector.app.features.html.EventHtmlRenderer
@@ -34,7 +35,6 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageTextContent
3435
import org.matrix.android.sdk.api.session.room.model.message.MessageType
3536
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
3637
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
37-
import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
3838
import org.matrix.android.sdk.api.session.room.timeline.getTextDisplayableContent
3939
import javax.inject.Inject
4040

@@ -60,7 +60,7 @@ class DisplayableEventFormatter @Inject constructor(
6060

6161
return when (timelineEvent.root.getClearType()) {
6262
EventType.MESSAGE -> {
63-
timelineEvent.getLastMessageContent()?.let { messageContent ->
63+
timelineEvent.getVectorLastMessageContent()?.let { messageContent ->
6464
when (messageContent.msgType) {
6565
MessageType.MSGTYPE_TEXT -> {
6666
val body = messageContent.getTextDisplayableContent()

0 commit comments

Comments
 (0)