diff --git a/library/ui-strings/src/main/res/values/strings.xml b/library/ui-strings/src/main/res/values/strings.xml index 7f11e63469..2d289150c6 100644 --- a/library/ui-strings/src/main/res/values/strings.xml +++ b/library/ui-strings/src/main/res/values/strings.xml @@ -134,6 +134,8 @@ ** Unable to decrypt: %s ** The sender\'s device has not sent us the keys for this message. + %1$s ended a voice broadcast. + @@ -3101,6 +3103,7 @@ (%1$s) Live + Live broadcast Buffering… Resume voice broadcast record diff --git a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt index 28ca761ace..7a3aa7cf8b 100644 --- a/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt +++ b/vector-app/src/main/java/im/vector/app/core/di/SingletonModule.kt @@ -48,6 +48,7 @@ import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.VectorAnalytics import im.vector.app.features.analytics.impl.DefaultVectorAnalytics import im.vector.app.features.analytics.metrics.VectorPlugins +import im.vector.app.features.configuration.VectorCustomEventTypesProvider import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator @@ -141,6 +142,7 @@ import javax.inject.Singleton vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider, flipperProxy: FlipperProxy, vectorPlugins: VectorPlugins, + vectorCustomEventTypesProvider: VectorCustomEventTypesProvider, ): MatrixConfiguration { return MatrixConfiguration( applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION, @@ -150,6 +152,7 @@ import javax.inject.Singleton flipperProxy.networkInterceptor(), ), metricPlugins = vectorPlugins.plugins(), + customEventTypesProvider = vectorCustomEventTypesProvider, ) } diff --git a/vector/src/main/java/im/vector/app/features/configuration/VectorCustomEventTypesProvider.kt b/vector/src/main/java/im/vector/app/features/configuration/VectorCustomEventTypesProvider.kt new file mode 100644 index 0000000000..55244685d7 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/configuration/VectorCustomEventTypesProvider.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.configuration + +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import org.matrix.android.sdk.api.provider.CustomEventTypesProvider +import javax.inject.Inject + +class VectorCustomEventTypesProvider @Inject constructor() : CustomEventTypesProvider { + + override val customPreviewableEventTypes = listOf( + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO + ) +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt index aaa0fc10c9..c8af85db4f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/timeline/format/DisplayableEventFormatter.kt @@ -21,8 +21,14 @@ import im.vector.app.EmojiSpanify 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.DrawableProvider import im.vector.app.core.resources.StringProvider import im.vector.app.features.html.EventHtmlRenderer +import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.isLive +import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent +import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent +import me.gujun.android.span.image import me.gujun.android.span.span import org.commonmark.node.Document import org.matrix.android.sdk.api.session.events.model.Event @@ -41,6 +47,7 @@ import javax.inject.Inject class DisplayableEventFormatter @Inject constructor( private val stringProvider: StringProvider, private val colorProvider: ColorProvider, + private val drawableProvider: DrawableProvider, private val emojiSpanify: EmojiSpanify, private val noticeEventFormatter: NoticeEventFormatter, private val htmlRenderer: Lazy @@ -135,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor( in EventType.STATE_ROOM_BEACON_INFO.values -> { simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor) } + VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> { + formatVoiceBroadcastEvent(timelineEvent.root.asVoiceBroadcastEvent(), senderName) + } else -> { span { text = noticeEventFormatter.format(timelineEvent, isDm) ?: "" @@ -252,4 +262,20 @@ class DisplayableEventFormatter @Inject constructor( body } } + + private fun formatVoiceBroadcastEvent(voiceBroadcastEvent: VoiceBroadcastEvent?, senderName: String): CharSequence { + return if (voiceBroadcastEvent?.isLive == true) { + span { + drawableProvider.getDrawable(R.drawable.ic_voice_broadcast, colorProvider.getColor(R.color.palette_vermilion))?.let { + image(it) + +" " + } + span(stringProvider.getString(R.string.voice_broadcast_live_broadcast)) { + textColor = colorProvider.getColor(R.color.palette_vermilion) + } + } + } else { + stringProvider.getString(R.string.notice_voice_broadcast_ended, senderName) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt index 638e3c185d..ca80530261 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomSummaryItemFactory.kt @@ -22,6 +22,7 @@ import com.airbnb.mvrx.Loading import im.vector.app.R import im.vector.app.core.date.DateFormatKind import im.vector.app.core.date.VectorDateFormatter +import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.epoxy.VectorEpoxyModel import im.vector.app.core.error.ErrorFormatter import im.vector.app.core.resources.StringProvider @@ -29,21 +30,30 @@ import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.app.features.home.room.typing.TypingHelper +import im.vector.app.features.voicebroadcast.isVoiceBroadcast +import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.getRoom +import org.matrix.android.sdk.api.session.room.getTimelineEvent import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent +import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent import org.matrix.android.sdk.api.util.toMatrixItem import javax.inject.Inject class RoomSummaryItemFactory @Inject constructor( + private val sessionHolder: ActiveSessionHolder, private val displayableEventFormatter: DisplayableEventFormatter, private val dateFormatter: VectorDateFormatter, private val stringProvider: StringProvider, private val typingHelper: TypingHelper, private val avatarRenderer: AvatarRenderer, - private val errorFormatter: ErrorFormatter + private val errorFormatter: ErrorFormatter, + private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase, ) { fun create( @@ -129,7 +139,7 @@ class RoomSummaryItemFactory @Inject constructor( val showSelected = selectedRoomIds.contains(roomSummary.roomId) var latestFormattedEvent: CharSequence = "" var latestEventTime = "" - val latestEvent = roomSummary.latestPreviewableEvent + val latestEvent = roomSummary.getVectorLatestPreviewableEvent() if (latestEvent != null) { latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not()) latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST) @@ -225,4 +235,13 @@ class RoomSummaryItemFactory @Inject constructor( else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1) } } + + private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? { + val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent + val liveVoiceBroadcastTimelineEvent = getOngoingVoiceBroadcastsUseCase.execute(roomId).lastOrNull() + ?.root?.eventId?.let { room.getTimelineEvent(it) } + return liveVoiceBroadcastTimelineEvent + ?: latestPreviewableEvent + ?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast + } } diff --git a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt index ec50618969..9974db470f 100644 --- a/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt +++ b/vector/src/main/java/im/vector/app/features/voicebroadcast/usecase/GetOngoingVoiceBroadcastsUseCase.kt @@ -18,8 +18,8 @@ package im.vector.app.features.voicebroadcast.usecase import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants +import im.vector.app.features.voicebroadcast.isLive import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent -import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.getRoom @@ -44,6 +44,6 @@ class GetOngoingVoiceBroadcastsUseCase @Inject constructor( QueryStringValue.IsNotEmpty ) .mapNotNull { it.asVoiceBroadcastEvent() } - .filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED } + .filter { it.isLive } } }