Merge pull request #7719 from vector-im/feature/fre/voice_broadcast_last_message
Voice Broadcast - Update last message in the room list
This commit is contained in:
commit
c74ea2dd16
|
@ -0,0 +1 @@
|
||||||
|
Voice Broadcast - Update last message in the room list
|
|
@ -134,6 +134,9 @@
|
||||||
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
<string name="notice_crypto_unable_to_decrypt">** Unable to decrypt: %s **</string>
|
||||||
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
<string name="notice_crypto_error_unknown_inbound_session_id">The sender\'s device has not sent us the keys for this message.</string>
|
||||||
|
|
||||||
|
<string name="notice_voice_broadcast_ended">%1$s ended a voice broadcast.</string>
|
||||||
|
<string name="notice_voice_broadcast_ended_by_you">You ended a voice broadcast.</string>
|
||||||
|
|
||||||
<!-- Messages -->
|
<!-- Messages -->
|
||||||
|
|
||||||
<!-- Room Screen -->
|
<!-- Room Screen -->
|
||||||
|
@ -3101,6 +3104,7 @@
|
||||||
<string name="audio_message_file_size">(%1$s)</string>
|
<string name="audio_message_file_size">(%1$s)</string>
|
||||||
|
|
||||||
<string name="voice_broadcast_live">Live</string>
|
<string name="voice_broadcast_live">Live</string>
|
||||||
|
<string name="voice_broadcast_live_broadcast">Live broadcast</string>
|
||||||
<!-- TODO Rename id to voice_broadcast_buffering -->
|
<!-- TODO Rename id to voice_broadcast_buffering -->
|
||||||
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
|
<string name="a11y_voice_broadcast_buffering">Buffering…</string>
|
||||||
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
<string name="a11y_resume_voice_broadcast_record">Resume voice broadcast record</string>
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package org.matrix.android.sdk.common
|
package org.matrix.android.sdk.common
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
|
|
||||||
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
|
class TestRoomDisplayNameFallbackProvider : RoomDisplayNameFallbackProvider {
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,9 @@ import okhttp3.ConnectionSpec
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
import org.matrix.android.sdk.api.crypto.MXCryptoConfig
|
||||||
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
import org.matrix.android.sdk.api.metrics.MetricPlugin
|
||||||
|
import org.matrix.android.sdk.api.provider.CustomEventTypesProvider
|
||||||
|
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
|
||||||
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
import java.net.Proxy
|
import java.net.Proxy
|
||||||
|
|
||||||
data class MatrixConfiguration(
|
data class MatrixConfiguration(
|
||||||
|
@ -75,9 +78,12 @@ data class MatrixConfiguration(
|
||||||
* Sync configuration.
|
* Sync configuration.
|
||||||
*/
|
*/
|
||||||
val syncConfig: SyncConfig = SyncConfig(),
|
val syncConfig: SyncConfig = SyncConfig(),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
* Metrics plugin that can be used to capture metrics from matrix-sdk-android.
|
||||||
*/
|
*/
|
||||||
val metricPlugins: List<MetricPlugin> = emptyList()
|
val metricPlugins: List<MetricPlugin> = emptyList(),
|
||||||
|
/**
|
||||||
|
* CustomEventTypesProvider to provide custom event types to the sdk which should be processed with internal events.
|
||||||
|
*/
|
||||||
|
val customEventTypesProvider: CustomEventTypesProvider? = null,
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2022 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide custom event types which should be processed with the internal event types.
|
||||||
|
*/
|
||||||
|
interface CustomEventTypesProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom event types to include when computing [RoomSummary.latestPreviewableEvent].
|
||||||
|
*/
|
||||||
|
val customPreviewableEventTypes: List<String>
|
||||||
|
}
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.matrix.android.sdk.api
|
package org.matrix.android.sdk.api.provider
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interface exists to let the implementation provide localized room display name fallback.
|
* This interface exists to let the implementation provide localized room display name fallback.
|
|
@ -22,6 +22,7 @@ import io.realm.RealmQuery
|
||||||
import io.realm.RealmResults
|
import io.realm.RealmResults
|
||||||
import io.realm.Sort
|
import io.realm.Sort
|
||||||
import io.realm.kotlin.where
|
import io.realm.kotlin.where
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||||
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
import org.matrix.android.sdk.internal.database.model.ChunkEntity
|
||||||
|
@ -94,6 +95,18 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
|
if (filters.filterTypes && filters.allowedTypes.isNotEmpty()) {
|
||||||
beginGroup()
|
beginGroup()
|
||||||
filters.allowedTypes.forEachIndexed { index, filter ->
|
filters.allowedTypes.forEachIndexed { index, filter ->
|
||||||
|
if (filter.eventType == EventType.ENCRYPTED) {
|
||||||
|
val otherTypes = filters.allowedTypes.minus(filter).map { it.eventType }
|
||||||
|
if (filter.stateKey == null) {
|
||||||
|
filterEncryptedTypes(otherTypes)
|
||||||
|
} else {
|
||||||
|
beginGroup()
|
||||||
|
filterEncryptedTypes(otherTypes)
|
||||||
|
and()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
||||||
|
endGroup()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
if (filter.stateKey == null) {
|
if (filter.stateKey == null) {
|
||||||
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, filter.eventType)
|
||||||
} else {
|
} else {
|
||||||
|
@ -103,6 +116,7 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
equalTo(TimelineEventEntityFields.ROOT.STATE_KEY, filter.stateKey)
|
||||||
endGroup()
|
endGroup()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (index != filters.allowedTypes.size - 1) {
|
if (index != filters.allowedTypes.size - 1) {
|
||||||
or()
|
or()
|
||||||
}
|
}
|
||||||
|
@ -115,7 +129,6 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
if (filters.filterEdits) {
|
if (filters.filterEdits) {
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.EDIT)
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.RESPONSE)
|
||||||
not().like(TimelineEventEntityFields.ROOT.CONTENT, TimelineEventFilter.Content.REFERENCE)
|
|
||||||
}
|
}
|
||||||
if (filters.filterRedacted) {
|
if (filters.filterRedacted) {
|
||||||
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
not().like(TimelineEventEntityFields.ROOT.UNSIGNED_DATA, TimelineEventFilter.Unsigned.REDACTED)
|
||||||
|
@ -124,6 +137,21 @@ internal fun RealmQuery<TimelineEventEntity>.filterEvents(filters: TimelineEvent
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun RealmQuery<TimelineEventEntity>.filterEncryptedTypes(allowedTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
||||||
|
beginGroup()
|
||||||
|
equalTo(TimelineEventEntityFields.ROOT.TYPE, EventType.ENCRYPTED)
|
||||||
|
and()
|
||||||
|
beginGroup()
|
||||||
|
isNull(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON)
|
||||||
|
allowedTypes.forEach { eventType ->
|
||||||
|
or()
|
||||||
|
like(TimelineEventEntityFields.ROOT.DECRYPTION_RESULT_JSON, TimelineEventFilter.DecryptedContent.type(eventType))
|
||||||
|
}
|
||||||
|
endGroup()
|
||||||
|
endGroup()
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
internal fun RealmQuery<TimelineEventEntity>.filterTypes(filterTypes: List<String>): RealmQuery<TimelineEventEntity> {
|
||||||
return if (filterTypes.isEmpty()) {
|
return if (filterTypes.isEmpty()) {
|
||||||
this
|
this
|
||||||
|
|
|
@ -34,6 +34,7 @@ internal object TimelineEventFilter {
|
||||||
*/
|
*/
|
||||||
internal object DecryptedContent {
|
internal object DecryptedContent {
|
||||||
internal const val URL = """{*"file":*"url":*}"""
|
internal const val URL = """{*"file":*"url":*}"""
|
||||||
|
fun type(type: String) = """{*"type":*"$type"*}"""
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,17 +17,23 @@
|
||||||
package org.matrix.android.sdk.internal.session.room.summary
|
package org.matrix.android.sdk.internal.session.room.summary
|
||||||
|
|
||||||
import io.realm.Realm
|
import io.realm.Realm
|
||||||
|
import org.matrix.android.sdk.api.MatrixConfiguration
|
||||||
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
|
import org.matrix.android.sdk.api.session.room.summary.RoomSummaryConstants
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
|
import org.matrix.android.sdk.api.session.room.timeline.EventTypeFilter
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEventFilters
|
||||||
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
||||||
import org.matrix.android.sdk.internal.database.query.latestEvent
|
import org.matrix.android.sdk.internal.database.query.latestEvent
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal object RoomSummaryEventsHelper {
|
internal class RoomSummaryEventsHelper @Inject constructor(
|
||||||
|
matrixConfiguration: MatrixConfiguration,
|
||||||
|
) {
|
||||||
|
|
||||||
private val previewFilters = TimelineEventFilters(
|
private val previewFilters = TimelineEventFilters(
|
||||||
filterTypes = true,
|
filterTypes = true,
|
||||||
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES.map { EventTypeFilter(eventType = it, stateKey = null) },
|
allowedTypes = RoomSummaryConstants.PREVIEWABLE_TYPES
|
||||||
|
.plus(matrixConfiguration.customEventTypesProvider?.customPreviewableEventTypes.orEmpty())
|
||||||
|
.map { EventTypeFilter(eventType = it, stateKey = null) },
|
||||||
filterUseless = true,
|
filterUseless = true,
|
||||||
filterRedacted = false,
|
filterRedacted = false,
|
||||||
filterEdits = true
|
filterEdits = true
|
||||||
|
|
|
@ -78,12 +78,13 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
private val crossSigningService: DefaultCrossSigningService,
|
private val crossSigningService: DefaultCrossSigningService,
|
||||||
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
private val roomAccountDataDataSource: RoomAccountDataDataSource,
|
||||||
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
private val homeServerCapabilitiesService: HomeServerCapabilitiesService,
|
||||||
|
private val roomSummaryEventsHelper: RoomSummaryEventsHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
|
fun refreshLatestPreviewContent(realm: Realm, roomId: String) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrNull(realm, roomId)
|
||||||
if (roomSummaryEntity != null) {
|
if (roomSummaryEntity != null) {
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
latestPreviewableEvent?.attemptToDecrypt()
|
latestPreviewableEvent?.attemptToDecrypt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,7 +146,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
val encryptionEvent = CurrentStateEventEntity.getOrNull(realm, roomId, type = EventType.STATE_ROOM_ENCRYPTION, stateKey = "")?.root
|
||||||
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
Timber.d("## CRYPTO: currentEncryptionEvent is $encryptionEvent")
|
||||||
|
|
||||||
val latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
val latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
|
|
||||||
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
val lastActivityFromEvent = latestPreviewableEvent?.root?.originServerTs
|
||||||
if (lastActivityFromEvent != null) {
|
if (lastActivityFromEvent != null) {
|
||||||
|
@ -231,7 +232,7 @@ internal class RoomSummaryUpdater @Inject constructor(
|
||||||
fun updateSendingInformation(realm: Realm, roomId: String) {
|
fun updateSendingInformation(realm: Realm, roomId: String) {
|
||||||
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
val roomSummaryEntity = RoomSummaryEntity.getOrCreate(realm, roomId)
|
||||||
roomSummaryEntity.updateHasFailedSending()
|
roomSummaryEntity.updateHasFailedSending()
|
||||||
roomSummaryEntity.latestPreviewableEvent = RoomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
roomSummaryEntity.latestPreviewableEvent = roomSummaryEventsHelper.getLatestPreviewableEvent(realm, roomId)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -49,6 +49,7 @@ import im.vector.app.features.analytics.VectorAnalytics
|
||||||
import im.vector.app.features.analytics.errors.ErrorTracker
|
import im.vector.app.features.analytics.errors.ErrorTracker
|
||||||
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
|
||||||
import im.vector.app.features.analytics.metrics.VectorPlugins
|
import im.vector.app.features.analytics.metrics.VectorPlugins
|
||||||
|
import im.vector.app.features.configuration.VectorCustomEventTypesProvider
|
||||||
import im.vector.app.features.invite.AutoAcceptInvites
|
import im.vector.app.features.invite.AutoAcceptInvites
|
||||||
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
|
import im.vector.app.features.invite.CompileTimeAutoAcceptInvites
|
||||||
import im.vector.app.features.navigation.DefaultNavigator
|
import im.vector.app.features.navigation.DefaultNavigator
|
||||||
|
@ -145,6 +146,7 @@ import javax.inject.Singleton
|
||||||
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
|
vectorRoomDisplayNameFallbackProvider: VectorRoomDisplayNameFallbackProvider,
|
||||||
flipperProxy: FlipperProxy,
|
flipperProxy: FlipperProxy,
|
||||||
vectorPlugins: VectorPlugins,
|
vectorPlugins: VectorPlugins,
|
||||||
|
vectorCustomEventTypesProvider: VectorCustomEventTypesProvider,
|
||||||
): MatrixConfiguration {
|
): MatrixConfiguration {
|
||||||
return MatrixConfiguration(
|
return MatrixConfiguration(
|
||||||
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
|
applicationFlavor = BuildConfig.FLAVOR_DESCRIPTION,
|
||||||
|
@ -154,6 +156,7 @@ import javax.inject.Singleton
|
||||||
flipperProxy.networkInterceptor(),
|
flipperProxy.networkInterceptor(),
|
||||||
),
|
),
|
||||||
metricPlugins = vectorPlugins.plugins(),
|
metricPlugins = vectorPlugins.plugins(),
|
||||||
|
customEventTypesProvider = vectorCustomEventTypesProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer
|
||||||
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayerImpl
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorderQ
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
|
@ -40,13 +40,13 @@ abstract class VoiceModule {
|
||||||
fun providesVoiceBroadcastRecorder(
|
fun providesVoiceBroadcastRecorder(
|
||||||
context: Context,
|
context: Context,
|
||||||
sessionHolder: ActiveSessionHolder,
|
sessionHolder: ActiveSessionHolder,
|
||||||
getMostRecentVoiceBroadcastStateEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
getVoiceBroadcastStateEventLiveUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
): VoiceBroadcastRecorder? {
|
): VoiceBroadcastRecorder? {
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
VoiceBroadcastRecorderQ(
|
VoiceBroadcastRecorderQ(
|
||||||
context = context,
|
context = context,
|
||||||
sessionHolder = sessionHolder,
|
sessionHolder = sessionHolder,
|
||||||
getVoiceBroadcastEventUseCase = getMostRecentVoiceBroadcastStateEventUseCase
|
getVoiceBroadcastEventUseCase = getVoiceBroadcastStateEventLiveUseCase
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.app.features.displayname
|
package im.vector.app.features.displayname
|
||||||
|
|
||||||
import org.matrix.android.sdk.api.MatrixItemDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.MatrixItemDisplayNameFallbackProvider
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
// Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration
|
// Used to provide the fallback to the MatrixSDK, in the MatrixConfiguration
|
||||||
|
|
|
@ -20,9 +20,15 @@ import dagger.Lazy
|
||||||
import im.vector.app.EmojiSpanify
|
import im.vector.app.EmojiSpanify
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.extensions.getVectorLastMessageContent
|
import im.vector.app.core.extensions.getVectorLastMessageContent
|
||||||
|
import im.vector.app.core.extensions.orEmpty
|
||||||
import im.vector.app.core.resources.ColorProvider
|
import im.vector.app.core.resources.ColorProvider
|
||||||
|
import im.vector.app.core.resources.DrawableProvider
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.html.EventHtmlRenderer
|
import im.vector.app.features.html.EventHtmlRenderer
|
||||||
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import me.gujun.android.span.image
|
||||||
import me.gujun.android.span.span
|
import me.gujun.android.span.span
|
||||||
import org.commonmark.node.Document
|
import org.commonmark.node.Document
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
@ -41,6 +47,7 @@ import javax.inject.Inject
|
||||||
class DisplayableEventFormatter @Inject constructor(
|
class DisplayableEventFormatter @Inject constructor(
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val colorProvider: ColorProvider,
|
private val colorProvider: ColorProvider,
|
||||||
|
private val drawableProvider: DrawableProvider,
|
||||||
private val emojiSpanify: EmojiSpanify,
|
private val emojiSpanify: EmojiSpanify,
|
||||||
private val noticeEventFormatter: NoticeEventFormatter,
|
private val noticeEventFormatter: NoticeEventFormatter,
|
||||||
private val htmlRenderer: Lazy<EventHtmlRenderer>
|
private val htmlRenderer: Lazy<EventHtmlRenderer>
|
||||||
|
@ -135,6 +142,9 @@ class DisplayableEventFormatter @Inject constructor(
|
||||||
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
in EventType.STATE_ROOM_BEACON_INFO.values -> {
|
||||||
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
|
simpleFormat(senderName, stringProvider.getString(R.string.sent_live_location), appendAuthor)
|
||||||
}
|
}
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> {
|
||||||
|
formatVoiceBroadcastEvent(timelineEvent.root, isDm, senderName)
|
||||||
|
}
|
||||||
else -> {
|
else -> {
|
||||||
span {
|
span {
|
||||||
text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
|
text = noticeEventFormatter.format(timelineEvent, isDm) ?: ""
|
||||||
|
@ -252,4 +262,20 @@ class DisplayableEventFormatter @Inject constructor(
|
||||||
body
|
body
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatVoiceBroadcastEvent(event: Event, isDm: Boolean, senderName: String): CharSequence {
|
||||||
|
return if (event.asVoiceBroadcastEvent()?.isLive == true) {
|
||||||
|
span {
|
||||||
|
drawableProvider.getDrawable(R.drawable.ic_voice_broadcast, colorProvider.getColor(R.color.palette_vermilion))?.let {
|
||||||
|
image(it)
|
||||||
|
+" "
|
||||||
|
}
|
||||||
|
span(stringProvider.getString(R.string.voice_broadcast_live_broadcast)) {
|
||||||
|
textColor = colorProvider.getColor(R.color.palette_vermilion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
noticeEventFormatter.format(event, senderName, isDm).orEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,8 @@ import im.vector.app.core.resources.StringProvider
|
||||||
import im.vector.app.features.roomprofile.permissions.RoleFormatter
|
import im.vector.app.features.roomprofile.permissions.RoleFormatter
|
||||||
import im.vector.app.features.settings.VectorPreferences
|
import im.vector.app.features.settings.VectorPreferences
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
import org.matrix.android.sdk.api.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
import org.matrix.android.sdk.api.extensions.appendNl
|
import org.matrix.android.sdk.api.extensions.appendNl
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -67,30 +69,32 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
|
private fun Event.isSentByCurrentUser() = senderId != null && senderId == currentUserId
|
||||||
|
|
||||||
fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? {
|
fun format(timelineEvent: TimelineEvent, isDm: Boolean): CharSequence? {
|
||||||
return when (val type = timelineEvent.root.getClearType()) {
|
val event = timelineEvent.root
|
||||||
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
val senderName = timelineEvent.senderInfo.disambiguatedDisplayName
|
||||||
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root, isDm)
|
return when (val type = event.getClearType()) {
|
||||||
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(event, isDm)
|
||||||
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_AVATAR -> formatRoomAvatarEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_THIRD_PARTY_INVITE -> formatRoomThirdPartyInvite(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_HISTORY_VISIBILITY ->
|
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(event, senderName)
|
||||||
formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_SERVER_ACL -> formatRoomServerAclEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(event, senderName, isDm)
|
||||||
|
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_WIDGET,
|
EventType.STATE_ROOM_WIDGET,
|
||||||
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_WIDGET_LEGACY -> formatWidgetEvent(event, senderName)
|
||||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName, isDm)
|
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
||||||
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.STATE_ROOM_POWER_LEVELS -> formatRoomPowerLevels(event, senderName)
|
||||||
EventType.CALL_INVITE,
|
EventType.CALL_INVITE,
|
||||||
EventType.CALL_CANDIDATES,
|
EventType.CALL_CANDIDATES,
|
||||||
EventType.CALL_HANGUP,
|
EventType.CALL_HANGUP,
|
||||||
EventType.CALL_REJECT,
|
EventType.CALL_REJECT,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(type, timelineEvent.root, timelineEvent.senderInfo.disambiguatedDisplayName)
|
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
|
||||||
EventType.CALL_NEGOTIATE,
|
EventType.CALL_NEGOTIATE,
|
||||||
EventType.CALL_SELECT_ANSWER,
|
EventType.CALL_SELECT_ANSWER,
|
||||||
EventType.CALL_REPLACES,
|
EventType.CALL_REPLACES,
|
||||||
|
@ -109,8 +113,7 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
EventType.STICKER,
|
EventType.STICKER,
|
||||||
in EventType.POLL_RESPONSE.values,
|
in EventType.POLL_RESPONSE.values,
|
||||||
in EventType.POLL_END.values,
|
in EventType.POLL_END.values,
|
||||||
in EventType.BEACON_LOCATION_DATA.values,
|
in EventType.BEACON_LOCATION_DATA.values -> formatDebug(event)
|
||||||
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatDebug(timelineEvent.root)
|
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
null
|
null
|
||||||
|
@ -191,6 +194,7 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
EventType.CALL_REJECT,
|
EventType.CALL_REJECT,
|
||||||
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
EventType.CALL_ANSWER -> formatCallEvent(type, event, senderName)
|
||||||
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(event, senderName, isDm)
|
||||||
|
VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO -> formatVoiceBroadcastEvent(event, senderName)
|
||||||
else -> {
|
else -> {
|
||||||
Timber.v("Type $type not handled by this formatter")
|
Timber.v("Type $type not handled by this formatter")
|
||||||
null
|
null
|
||||||
|
@ -894,4 +898,16 @@ class NoticeEventFormatter @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun formatVoiceBroadcastEvent(event: Event, senderName: String?): CharSequence {
|
||||||
|
return if (event.asVoiceBroadcastEvent()?.content?.voiceBroadcastState == VoiceBroadcastState.STOPPED) {
|
||||||
|
if (event.isSentByCurrentUser()) {
|
||||||
|
sp.getString(R.string.notice_voice_broadcast_ended_by_you)
|
||||||
|
} else {
|
||||||
|
sp.getString(R.string.notice_voice_broadcast_ended, senderName)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
formatDebug(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -252,7 +252,7 @@ class TimelineEventVisibilityHelper @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
|
if (root.getClearType() == VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO &&
|
||||||
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState != VoiceBroadcastState.STARTED) {
|
root.asVoiceBroadcastEvent()?.content?.voiceBroadcastState !in arrayOf(VoiceBroadcastState.STARTED, VoiceBroadcastState.STOPPED)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import com.airbnb.mvrx.Loading
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import im.vector.app.core.date.DateFormatKind
|
import im.vector.app.core.date.DateFormatKind
|
||||||
import im.vector.app.core.date.VectorDateFormatter
|
import im.vector.app.core.date.VectorDateFormatter
|
||||||
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.error.ErrorFormatter
|
import im.vector.app.core.error.ErrorFormatter
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.core.resources.StringProvider
|
||||||
|
@ -29,21 +30,33 @@ import im.vector.app.features.home.AvatarRenderer
|
||||||
import im.vector.app.features.home.RoomListDisplayMode
|
import im.vector.app.features.home.RoomListDisplayMode
|
||||||
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
import im.vector.app.features.home.room.detail.timeline.format.DisplayableEventFormatter
|
||||||
import im.vector.app.features.home.room.typing.TypingHelper
|
import im.vector.app.features.home.room.typing.TypingHelper
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
|
import im.vector.app.features.voicebroadcast.isVoiceBroadcast
|
||||||
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
import im.vector.lib.core.utils.epoxy.charsequence.toEpoxyCharSequence
|
||||||
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
|
import org.matrix.android.sdk.api.session.room.getTimelineEvent
|
||||||
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
import org.matrix.android.sdk.api.session.room.model.RoomSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.message.asMessageAudioEvent
|
||||||
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
import org.matrix.android.sdk.api.util.toMatrixItem
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class RoomSummaryItemFactory @Inject constructor(
|
class RoomSummaryItemFactory @Inject constructor(
|
||||||
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val displayableEventFormatter: DisplayableEventFormatter,
|
private val displayableEventFormatter: DisplayableEventFormatter,
|
||||||
private val dateFormatter: VectorDateFormatter,
|
private val dateFormatter: VectorDateFormatter,
|
||||||
private val stringProvider: StringProvider,
|
private val stringProvider: StringProvider,
|
||||||
private val typingHelper: TypingHelper,
|
private val typingHelper: TypingHelper,
|
||||||
private val avatarRenderer: AvatarRenderer,
|
private val avatarRenderer: AvatarRenderer,
|
||||||
private val errorFormatter: ErrorFormatter
|
private val errorFormatter: ErrorFormatter,
|
||||||
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
@ -129,13 +142,15 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
val showSelected = selectedRoomIds.contains(roomSummary.roomId)
|
||||||
var latestFormattedEvent: CharSequence = ""
|
var latestFormattedEvent: CharSequence = ""
|
||||||
var latestEventTime = ""
|
var latestEventTime = ""
|
||||||
val latestEvent = roomSummary.latestPreviewableEvent
|
val latestEvent = roomSummary.getVectorLatestPreviewableEvent()
|
||||||
if (latestEvent != null) {
|
if (latestEvent != null) {
|
||||||
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
latestFormattedEvent = displayableEventFormatter.format(latestEvent, roomSummary.isDirect, roomSummary.isDirect.not())
|
||||||
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
latestEventTime = dateFormatter.format(latestEvent.root.originServerTs, DateFormatKind.ROOM_LIST)
|
||||||
}
|
}
|
||||||
|
|
||||||
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
|
||||||
|
// Skip typing while there is a live voice broadcast
|
||||||
|
.takeUnless { latestEvent?.root?.asVoiceBroadcastEvent()?.isLive.orFalse() }.orEmpty()
|
||||||
|
|
||||||
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
return if (subtitle.isBlank() && displayMode == RoomListDisplayMode.FILTERED) {
|
||||||
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
createCenteredRoomSummaryItem(roomSummary, displayMode, showSelected, unreadCount, onClick, onLongClick)
|
||||||
|
@ -225,4 +240,14 @@ class RoomSummaryItemFactory @Inject constructor(
|
||||||
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
else -> stringProvider.getQuantityString(R.plurals.search_space_multiple_parents, size - 1, directParentNames[0], size - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun RoomSummary.getVectorLatestPreviewableEvent(): TimelineEvent? {
|
||||||
|
val room = sessionHolder.getSafeActiveSession()?.getRoom(roomId) ?: return latestPreviewableEvent
|
||||||
|
val liveVoiceBroadcastTimelineEvent = getRoomLiveVoiceBroadcastsUseCase.execute(roomId).lastOrNull()
|
||||||
|
?.root?.eventId?.let { room.getTimelineEvent(it) }
|
||||||
|
return latestPreviewableEvent?.takeIf { it.root.getClearType() == EventType.CALL_INVITE }
|
||||||
|
?: liveVoiceBroadcastTimelineEvent
|
||||||
|
?: latestPreviewableEvent
|
||||||
|
?.takeUnless { it.root.asMessageAudioEvent()?.isVoiceBroadcast().orFalse() } // Skip voice messages related to voice broadcast
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ package im.vector.app.features.room
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
import org.matrix.android.sdk.api.RoomDisplayNameFallbackProvider
|
import org.matrix.android.sdk.api.provider.RoomDisplayNameFallbackProvider
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class VectorRoomDisplayNameFallbackProvider @Inject constructor(
|
class VectorRoomDisplayNameFallbackProvider @Inject constructor(
|
||||||
|
|
|
@ -31,7 +31,7 @@ import im.vector.app.features.voicebroadcast.listening.VoiceBroadcastPlayer.Stat
|
||||||
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
|
import im.vector.app.features.voicebroadcast.listening.usecase.GetLiveVoiceBroadcastChunksUseCase
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -48,7 +48,7 @@ import javax.inject.Singleton
|
||||||
class VoiceBroadcastPlayerImpl @Inject constructor(
|
class VoiceBroadcastPlayerImpl @Inject constructor(
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val playbackTracker: AudioMessagePlaybackTracker,
|
private val playbackTracker: AudioMessagePlaybackTracker,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
|
private val getLiveVoiceBroadcastChunksUseCase: GetLiveVoiceBroadcastChunksUseCase
|
||||||
) : VoiceBroadcastPlayer {
|
) : VoiceBroadcastPlayer {
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.sequence
|
import im.vector.app.features.voicebroadcast.sequence
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
import im.vector.app.features.voicebroadcast.voiceBroadcastId
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
@ -48,7 +48,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
|
class GetLiveVoiceBroadcastChunksUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase,
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {
|
fun execute(voiceBroadcast: VoiceBroadcast): Flow<List<MessageAudioEvent>> {
|
||||||
|
|
|
@ -26,7 +26,7 @@ import im.vector.app.features.voice.AbstractVoiceRecorderQ
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetMostRecentVoiceBroadcastStateEventUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetVoiceBroadcastStateEventLiveUseCase
|
||||||
import im.vector.lib.core.utils.timer.CountUpTimer
|
import im.vector.lib.core.utils.timer.CountUpTimer
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
@ -40,7 +40,7 @@ import java.util.concurrent.TimeUnit
|
||||||
class VoiceBroadcastRecorderQ(
|
class VoiceBroadcastRecorderQ(
|
||||||
context: Context,
|
context: Context,
|
||||||
private val sessionHolder: ActiveSessionHolder,
|
private val sessionHolder: ActiveSessionHolder,
|
||||||
private val getVoiceBroadcastEventUseCase: GetMostRecentVoiceBroadcastStateEventUseCase
|
private val getVoiceBroadcastEventUseCase: GetVoiceBroadcastStateEventLiveUseCase
|
||||||
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
) : AbstractVoiceRecorderQ(context), VoiceBroadcastRecorder {
|
||||||
|
|
||||||
private val session get() = sessionHolder.getActiveSession()
|
private val session get() = sessionHolder.getActiveSession()
|
||||||
|
|
|
@ -28,7 +28,7 @@ import im.vector.app.features.voicebroadcast.model.VoiceBroadcast
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastChunk
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
||||||
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
import im.vector.app.features.voicebroadcast.recording.VoiceBroadcastRecorder
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
import im.vector.lib.multipicker.utils.toMultiPickerAudioType
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -56,7 +56,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
private val voiceBroadcastRecorder: VoiceBroadcastRecorder?,
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val buildMeta: BuildMeta,
|
private val buildMeta: BuildMeta,
|
||||||
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase,
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
private val stopVoiceBroadcastUseCase: StopVoiceBroadcastUseCase,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ class StartVoiceBroadcastUseCase @Inject constructor(
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast")
|
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: another voice broadcast")
|
||||||
throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting
|
throw VoiceBroadcastFailure.RecordingError.UserAlreadyBroadcasting
|
||||||
}
|
}
|
||||||
getOngoingVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> {
|
getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId).isNotEmpty() -> {
|
||||||
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting")
|
Timber.d("## StartVoiceBroadcastUseCase: Cannot start voice broadcast: user already broadcasting")
|
||||||
throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse
|
throw VoiceBroadcastFailure.RecordingError.BlockedBySomeoneElse
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,7 +19,7 @@ package im.vector.app.features.voicebroadcast.recording.usecase
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastHelper
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.usecase.GetOngoingVoiceBroadcastsUseCase
|
import im.vector.app.features.voicebroadcast.usecase.GetRoomLiveVoiceBroadcastsUseCase
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import org.matrix.android.sdk.api.session.room.model.Membership
|
import org.matrix.android.sdk.api.session.room.model.Membership
|
||||||
|
@ -32,7 +32,7 @@ import javax.inject.Inject
|
||||||
*/
|
*/
|
||||||
class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val getOngoingVoiceBroadcastsUseCase: GetOngoingVoiceBroadcastsUseCase,
|
private val getRoomLiveVoiceBroadcastsUseCase: GetRoomLiveVoiceBroadcastsUseCase,
|
||||||
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
private val voiceBroadcastHelper: VoiceBroadcastHelper,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class StopOngoingVoiceBroadcastUseCase @Inject constructor(
|
||||||
|
|
||||||
recentRooms
|
recentRooms
|
||||||
.forEach { room ->
|
.forEach { room ->
|
||||||
val ongoingVoiceBroadcasts = getOngoingVoiceBroadcastsUseCase.execute(room.roomId)
|
val ongoingVoiceBroadcasts = getRoomLiveVoiceBroadcastsUseCase.execute(room.roomId)
|
||||||
val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId
|
val myOngoingVoiceBroadcastId = ongoingVoiceBroadcasts.find { it.root.stateKey == session.myUserId }?.reference?.eventId
|
||||||
val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() }
|
val initialEvent = myOngoingVoiceBroadcastId?.let { room.timelineService().getTimelineEvent(it)?.root?.asVoiceBroadcastEvent() }
|
||||||
if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) {
|
if (myOngoingVoiceBroadcastId != null && initialEvent?.content?.deviceId == session.sessionParams.deviceId) {
|
||||||
|
|
|
@ -18,32 +18,26 @@ package im.vector.app.features.voicebroadcast.usecase
|
||||||
|
|
||||||
import im.vector.app.core.di.ActiveSessionHolder
|
import im.vector.app.core.di.ActiveSessionHolder
|
||||||
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
import im.vector.app.features.voicebroadcast.VoiceBroadcastConstants
|
||||||
|
import im.vector.app.features.voicebroadcast.isLive
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastEvent
|
||||||
import im.vector.app.features.voicebroadcast.model.VoiceBroadcastState
|
|
||||||
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
import im.vector.app.features.voicebroadcast.model.asVoiceBroadcastEvent
|
||||||
import org.matrix.android.sdk.api.query.QueryStringValue
|
import org.matrix.android.sdk.api.query.QueryStringValue
|
||||||
import org.matrix.android.sdk.api.session.getRoom
|
import org.matrix.android.sdk.api.session.getRoom
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetOngoingVoiceBroadcastsUseCase @Inject constructor(
|
class GetRoomLiveVoiceBroadcastsUseCase @Inject constructor(
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
fun execute(roomId: String): List<VoiceBroadcastEvent> {
|
||||||
val session = activeSessionHolder.getSafeActiveSession() ?: run {
|
val session = activeSessionHolder.getSafeActiveSession() ?: return emptyList()
|
||||||
Timber.d("## GetOngoingVoiceBroadcastsUseCase: no active session")
|
|
||||||
return emptyList()
|
|
||||||
}
|
|
||||||
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
val room = session.getRoom(roomId) ?: error("Unknown roomId: $roomId")
|
||||||
|
|
||||||
Timber.d("## GetLastVoiceBroadcastUseCase: get last voice broadcast in $roomId")
|
|
||||||
|
|
||||||
return room.stateService().getStateEvents(
|
return room.stateService().getStateEvents(
|
||||||
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
setOf(VoiceBroadcastConstants.STATE_ROOM_VOICE_BROADCAST_INFO),
|
||||||
QueryStringValue.IsNotEmpty
|
QueryStringValue.IsNotEmpty
|
||||||
)
|
)
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
.filter { it.content?.voiceBroadcastState != null && it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
.filter { it.isLive }
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -42,7 +42,7 @@ import org.matrix.android.sdk.flow.mapOptional
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class GetMostRecentVoiceBroadcastStateEventUseCase @Inject constructor(
|
class GetVoiceBroadcastStateEventLiveUseCase @Inject constructor(
|
||||||
private val session: Session,
|
private val session: Session,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -52,14 +52,14 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
private val fakeRoom = FakeRoom()
|
private val fakeRoom = FakeRoom()
|
||||||
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
private val fakeSession = FakeSession(fakeRoomService = FakeRoomService(fakeRoom))
|
||||||
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
private val fakeVoiceBroadcastRecorder = mockk<VoiceBroadcastRecorder>(relaxed = true)
|
||||||
private val fakeGetOngoingVoiceBroadcastsUseCase = mockk<GetOngoingVoiceBroadcastsUseCase>()
|
private val fakeGetRoomLiveVoiceBroadcastsUseCase = mockk<GetRoomLiveVoiceBroadcastsUseCase>()
|
||||||
private val startVoiceBroadcastUseCase = spyk(
|
private val startVoiceBroadcastUseCase = spyk(
|
||||||
StartVoiceBroadcastUseCase(
|
StartVoiceBroadcastUseCase(
|
||||||
session = fakeSession,
|
session = fakeSession,
|
||||||
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
|
voiceBroadcastRecorder = fakeVoiceBroadcastRecorder,
|
||||||
context = FakeContext().instance,
|
context = FakeContext().instance,
|
||||||
buildMeta = mockk(),
|
buildMeta = mockk(),
|
||||||
getOngoingVoiceBroadcastsUseCase = fakeGetOngoingVoiceBroadcastsUseCase,
|
getRoomLiveVoiceBroadcastsUseCase = fakeGetRoomLiveVoiceBroadcastsUseCase,
|
||||||
stopVoiceBroadcastUseCase = mockk()
|
stopVoiceBroadcastUseCase = mockk()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -140,7 +140,7 @@ class StartVoiceBroadcastUseCaseTest {
|
||||||
}
|
}
|
||||||
.mapNotNull { it.asVoiceBroadcastEvent() }
|
.mapNotNull { it.asVoiceBroadcastEvent() }
|
||||||
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
.filter { it.content?.voiceBroadcastState != VoiceBroadcastState.STOPPED }
|
||||||
every { fakeGetOngoingVoiceBroadcastsUseCase.execute(any()) } returns events
|
every { fakeGetRoomLiveVoiceBroadcastsUseCase.execute(any()) } returns events
|
||||||
}
|
}
|
||||||
|
|
||||||
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)
|
private data class VoiceBroadcast(val userId: String, val state: VoiceBroadcastState)
|
||||||
|
|
Loading…
Reference in New Issue