mirror of
https://github.com/SchildiChat/SchildiChat-android.git
synced 2025-02-17 12:30:07 +03:00
Jitsi call: start using call tiles
This commit is contained in:
parent
cdf97fc29f
commit
b7e5a6cf28
14 changed files with 259 additions and 148 deletions
|
@ -24,18 +24,31 @@ import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.facebook.react.bridge.JavaOnlyMap
|
||||||
|
import org.jitsi.meet.sdk.BroadcastEmitter
|
||||||
import org.jitsi.meet.sdk.BroadcastEvent
|
import org.jitsi.meet.sdk.BroadcastEvent
|
||||||
|
import org.jitsi.meet.sdk.JitsiMeet
|
||||||
import org.matrix.android.sdk.api.extensions.tryOrNull
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
|
||||||
|
private const val CONFERENCE_URL_DATA_KEY = "url"
|
||||||
|
|
||||||
fun BroadcastEvent.extractConferenceUrl(): String? {
|
fun BroadcastEvent.extractConferenceUrl(): String? {
|
||||||
return when (type) {
|
return when (type) {
|
||||||
BroadcastEvent.Type.CONFERENCE_TERMINATED,
|
BroadcastEvent.Type.CONFERENCE_TERMINATED,
|
||||||
BroadcastEvent.Type.CONFERENCE_WILL_JOIN,
|
BroadcastEvent.Type.CONFERENCE_WILL_JOIN,
|
||||||
BroadcastEvent.Type.CONFERENCE_JOINED -> data["url"] as? String
|
BroadcastEvent.Type.CONFERENCE_JOINED -> data[CONFERENCE_URL_DATA_KEY] as? String
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class JitsiBroadcastEmitter(private val context: Context) {
|
||||||
|
|
||||||
|
fun emitConferenceEnded() {
|
||||||
|
val broadcastEventData = JavaOnlyMap.of(CONFERENCE_URL_DATA_KEY, JitsiMeet.getCurrentConference())
|
||||||
|
BroadcastEmitter(context).sendBroadcast(BroadcastEvent.Type.CONFERENCE_TERMINATED.name, broadcastEventData)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class JitsiBroadcastEventObserver(private val context: Context,
|
class JitsiBroadcastEventObserver(private val context: Context,
|
||||||
private val onBroadcastEvent: (BroadcastEvent) -> Unit) : LifecycleObserver {
|
private val onBroadcastEvent: (BroadcastEvent) -> Unit) : LifecycleObserver {
|
||||||
|
|
|
@ -31,7 +31,6 @@ import com.airbnb.mvrx.Fail
|
||||||
import com.airbnb.mvrx.MvRx
|
import com.airbnb.mvrx.MvRx
|
||||||
import com.airbnb.mvrx.Success
|
import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.viewModel
|
import com.airbnb.mvrx.viewModel
|
||||||
import com.facebook.react.bridge.JavaOnlyMap
|
|
||||||
import com.facebook.react.modules.core.PermissionListener
|
import com.facebook.react.modules.core.PermissionListener
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import im.vector.app.R
|
import im.vector.app.R
|
||||||
|
@ -40,7 +39,6 @@ import im.vector.app.core.extensions.exhaustive
|
||||||
import im.vector.app.core.platform.VectorBaseActivity
|
import im.vector.app.core.platform.VectorBaseActivity
|
||||||
import im.vector.app.databinding.ActivityJitsiBinding
|
import im.vector.app.databinding.ActivityJitsiBinding
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
import org.jitsi.meet.sdk.BroadcastEmitter
|
|
||||||
import org.jitsi.meet.sdk.BroadcastEvent
|
import org.jitsi.meet.sdk.BroadcastEvent
|
||||||
import org.jitsi.meet.sdk.JitsiMeet
|
import org.jitsi.meet.sdk.JitsiMeet
|
||||||
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
|
import org.jitsi.meet.sdk.JitsiMeetActivityDelegate
|
||||||
|
@ -115,8 +113,7 @@ class VectorJitsiActivity : VectorBaseActivity<ActivityJitsiBinding>(), JitsiMee
|
||||||
jitsiMeetView?.dispose()
|
jitsiMeetView?.dispose()
|
||||||
// Fake emitting CONFERENCE_TERMINATED event when currentConf is not null (probably when closing the PiP screen).
|
// Fake emitting CONFERENCE_TERMINATED event when currentConf is not null (probably when closing the PiP screen).
|
||||||
if (currentConf != null) {
|
if (currentConf != null) {
|
||||||
val broadcastEventData = JavaOnlyMap.of("url", currentConf)
|
JitsiBroadcastEmitter(this).emitConferenceEnded()
|
||||||
BroadcastEmitter(this).sendBroadcast(BroadcastEvent.Type.CONFERENCE_TERMINATED.name, broadcastEventData)
|
|
||||||
}
|
}
|
||||||
JitsiMeetActivityDelegate.onHostDestroy(this)
|
JitsiMeetActivityDelegate.onHostDestroy(this)
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
|
|
|
@ -27,6 +27,7 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageWithAttachme
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
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.widgets.model.Widget
|
import org.matrix.android.sdk.api.session.widgets.model.Widget
|
||||||
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
|
|
||||||
sealed class RoomDetailAction : VectorViewModelAction {
|
sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
|
@ -90,6 +91,10 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
||||||
object ManageIntegrations : RoomDetailAction()
|
object ManageIntegrations : RoomDetailAction()
|
||||||
data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
|
data class AddJitsiWidget(val withVideo: Boolean) : RoomDetailAction()
|
||||||
data class RemoveWidget(val widgetId: String) : RoomDetailAction()
|
data class RemoveWidget(val widgetId: String) : RoomDetailAction()
|
||||||
|
|
||||||
|
object JoinJitsiCall: RoomDetailAction()
|
||||||
|
object LeaveJitsiCall: RoomDetailAction()
|
||||||
|
|
||||||
data class EnsureNativeWidgetAllowed(val widget: Widget,
|
data class EnsureNativeWidgetAllowed(val widget: Widget,
|
||||||
val userJustAccepted: Boolean,
|
val userJustAccepted: Boolean,
|
||||||
val grantedEvents: RoomDetailViewEvents) : RoomDetailAction()
|
val grantedEvents: RoomDetailViewEvents) : RoomDetailAction()
|
||||||
|
|
|
@ -67,6 +67,7 @@ import com.airbnb.mvrx.Success
|
||||||
import com.airbnb.mvrx.args
|
import com.airbnb.mvrx.args
|
||||||
import com.airbnb.mvrx.fragmentViewModel
|
import com.airbnb.mvrx.fragmentViewModel
|
||||||
import com.airbnb.mvrx.withState
|
import com.airbnb.mvrx.withState
|
||||||
|
import com.facebook.react.bridge.JavaOnlyMap
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.jakewharton.rxbinding3.view.focusChanges
|
import com.jakewharton.rxbinding3.view.focusChanges
|
||||||
import com.jakewharton.rxbinding3.widget.textChanges
|
import com.jakewharton.rxbinding3.widget.textChanges
|
||||||
|
@ -120,6 +121,7 @@ import im.vector.app.features.attachments.preview.AttachmentsPreviewArgs
|
||||||
import im.vector.app.features.attachments.toGroupedContentAttachmentData
|
import im.vector.app.features.attachments.toGroupedContentAttachmentData
|
||||||
import im.vector.app.features.call.SharedKnownCallsViewModel
|
import im.vector.app.features.call.SharedKnownCallsViewModel
|
||||||
import im.vector.app.features.call.VectorCallActivity
|
import im.vector.app.features.call.VectorCallActivity
|
||||||
|
import im.vector.app.features.call.conference.JitsiBroadcastEmitter
|
||||||
import im.vector.app.features.call.conference.JitsiBroadcastEventObserver
|
import im.vector.app.features.call.conference.JitsiBroadcastEventObserver
|
||||||
import im.vector.app.features.call.conference.JitsiCallViewModel
|
import im.vector.app.features.call.conference.JitsiCallViewModel
|
||||||
import im.vector.app.features.call.conference.extractConferenceUrl
|
import im.vector.app.features.call.conference.extractConferenceUrl
|
||||||
|
@ -176,6 +178,7 @@ import nl.dionsegijn.konfetti.models.Shape
|
||||||
import nl.dionsegijn.konfetti.models.Size
|
import nl.dionsegijn.konfetti.models.Size
|
||||||
import org.billcarsonfr.jsonviewer.JSonViewerDialog
|
import org.billcarsonfr.jsonviewer.JSonViewerDialog
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
|
import org.jitsi.meet.sdk.BroadcastEmitter
|
||||||
import org.jitsi.meet.sdk.BroadcastEvent
|
import org.jitsi.meet.sdk.BroadcastEvent
|
||||||
import org.matrix.android.sdk.api.session.Session
|
import org.matrix.android.sdk.api.session.Session
|
||||||
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
import org.matrix.android.sdk.api.session.content.ContentAttachmentData
|
||||||
|
@ -394,6 +397,7 @@ class RoomDetailFragment @Inject constructor(
|
||||||
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
|
RoomDetailViewEvents.OpenActiveWidgetBottomSheet -> onViewWidgetsClicked()
|
||||||
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
|
is RoomDetailViewEvents.ShowInfoOkDialog -> showDialogWithMessage(it.message)
|
||||||
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
|
is RoomDetailViewEvents.JoinJitsiConference -> joinJitsiRoom(it.widget, it.withVideo)
|
||||||
|
RoomDetailViewEvents.LeaveJitsiConference -> leaveJitsiConference()
|
||||||
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
|
RoomDetailViewEvents.ShowWaitingView -> vectorBaseActivity.showWaitingView()
|
||||||
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
RoomDetailViewEvents.HideWaitingView -> vectorBaseActivity.hideWaitingView()
|
||||||
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
is RoomDetailViewEvents.RequestNativeWidgetPermission -> requestNativeWidgetPermission(it)
|
||||||
|
@ -416,6 +420,10 @@ class RoomDetailFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun leaveJitsiConference() {
|
||||||
|
JitsiBroadcastEmitter(vectorBaseActivity).emitConferenceEnded()
|
||||||
|
}
|
||||||
|
|
||||||
private fun onBroadcastEvent(event: BroadcastEvent) {
|
private fun onBroadcastEvent(event: BroadcastEvent) {
|
||||||
roomDetailViewModel.handle(RoomDetailAction.UpdateJoinJitsiCallStatus(event))
|
roomDetailViewModel.handle(RoomDetailAction.UpdateJoinJitsiCallStatus(event))
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ sealed class RoomDetailViewEvents : VectorViewEvents {
|
||||||
|
|
||||||
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
data class NavigateToEvent(val eventId: String) : RoomDetailViewEvents()
|
||||||
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
|
data class JoinJitsiConference(val widget: Widget, val withVideo: Boolean) : RoomDetailViewEvents()
|
||||||
|
object LeaveJitsiConference : RoomDetailViewEvents()
|
||||||
|
|
||||||
object OpenInvitePeople : RoomDetailViewEvents()
|
object OpenInvitePeople : RoomDetailViewEvents()
|
||||||
object OpenSetRoomAvatarDialog : RoomDetailViewEvents()
|
object OpenSetRoomAvatarDialog : RoomDetailViewEvents()
|
||||||
|
|
|
@ -65,6 +65,7 @@ import kotlinx.coroutines.withContext
|
||||||
import org.commonmark.parser.Parser
|
import org.commonmark.parser.Parser
|
||||||
import org.commonmark.renderer.html.HtmlRenderer
|
import org.commonmark.renderer.html.HtmlRenderer
|
||||||
import org.jitsi.meet.sdk.BroadcastEvent
|
import org.jitsi.meet.sdk.BroadcastEvent
|
||||||
|
import org.jitsi.meet.sdk.JitsiMeet
|
||||||
import org.matrix.android.sdk.api.MatrixCallback
|
import org.matrix.android.sdk.api.MatrixCallback
|
||||||
import org.matrix.android.sdk.api.MatrixPatterns
|
import org.matrix.android.sdk.api.MatrixPatterns
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
import org.matrix.android.sdk.api.extensions.orFalse
|
||||||
|
@ -240,10 +241,17 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
widgets.filter { it.isActive }
|
widgets.filter { it.isActive }
|
||||||
}
|
}
|
||||||
.execute { widgets ->
|
.execute { widgets ->
|
||||||
val jitsiConfId = widgets()?.firstOrNull { it.type == WidgetType.Jitsi }?.let { jitsiWidget ->
|
val jitsiWidget = widgets()?.firstOrNull { it.type == WidgetType.Jitsi }
|
||||||
jitsiService.extractProperties(jitsiWidget)?.confId
|
val jitsiConfId = jitsiWidget?.let {
|
||||||
|
jitsiService.extractProperties(it)?.confId
|
||||||
}
|
}
|
||||||
copy(activeRoomWidgets = widgets, jitsiConfId = jitsiConfId)
|
copy(
|
||||||
|
activeRoomWidgets = widgets,
|
||||||
|
jitsiState = jitsiState.copy(
|
||||||
|
confId = jitsiConfId,
|
||||||
|
widgetId = jitsiWidget?.widgetId
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,6 +318,8 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
is RoomDetailAction.ManageIntegrations -> handleManageIntegrations()
|
||||||
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
is RoomDetailAction.AddJitsiWidget -> handleAddJitsiConference(action)
|
||||||
is RoomDetailAction.UpdateJoinJitsiCallStatus -> handleJitsiCallJoinStatus(action)
|
is RoomDetailAction.UpdateJoinJitsiCallStatus -> handleJitsiCallJoinStatus(action)
|
||||||
|
is RoomDetailAction.JoinJitsiCall -> handleJoinJitsiCall()
|
||||||
|
is RoomDetailAction.LeaveJitsiCall -> handleLeaveJitsiCall()
|
||||||
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
is RoomDetailAction.RemoveWidget -> handleDeleteWidget(action.widgetId)
|
||||||
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
is RoomDetailAction.EnsureNativeWidgetAllowed -> handleCheckWidgetAllowed(action)
|
||||||
is RoomDetailAction.CancelSend -> handleCancel(action)
|
is RoomDetailAction.CancelSend -> handleCancel(action)
|
||||||
|
@ -331,24 +341,34 @@ class RoomDetailViewModel @AssistedInject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
|
private fun handleJitsiCallJoinStatus(action: RoomDetailAction.UpdateJoinJitsiCallStatus) = withState { state ->
|
||||||
if (state.jitsiConfId == null) {
|
if (state.jitsiState.confId == null) {
|
||||||
// If jitsi widget is removed while on the call
|
// If jitsi widget is removed while on the call
|
||||||
if (state.hasJoinedActiveJitsiConference) {
|
if (state.jitsiState.hasJoined) {
|
||||||
setState { copy(hasJoinedActiveJitsiConference = false) }
|
setState { copy(jitsiState = jitsiState.copy(hasJoined = false)) }
|
||||||
}
|
}
|
||||||
return@withState
|
return@withState
|
||||||
}
|
}
|
||||||
when (action.broadcastEvent.type) {
|
when (action.broadcastEvent.type) {
|
||||||
BroadcastEvent.Type.CONFERENCE_JOINED,
|
BroadcastEvent.Type.CONFERENCE_JOINED,
|
||||||
BroadcastEvent.Type.CONFERENCE_TERMINATED -> {
|
BroadcastEvent.Type.CONFERENCE_TERMINATED -> {
|
||||||
if (action.broadcastEvent.extractConferenceUrl()?.endsWith(state.jitsiConfId).orFalse()) {
|
if (action.broadcastEvent.extractConferenceUrl()?.endsWith(state.jitsiState.confId).orFalse()) {
|
||||||
setState { copy(hasJoinedActiveJitsiConference = action.broadcastEvent.type == BroadcastEvent.Type.CONFERENCE_JOINED) }
|
setState { copy(jitsiState = jitsiState.copy(hasJoined = action.broadcastEvent.type == BroadcastEvent.Type.CONFERENCE_JOINED)) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleLeaveJitsiCall() {
|
||||||
|
_viewEvents.post(RoomDetailViewEvents.LeaveJitsiConference)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleJoinJitsiCall() = withState{ state ->
|
||||||
|
val jitsiWidget = state.activeRoomWidgets()?.firstOrNull { it.widgetId == state.jitsiState.widgetId} ?: return@withState
|
||||||
|
val action = RoomDetailAction.EnsureNativeWidgetAllowed(jitsiWidget, false, RoomDetailViewEvents.JoinJitsiConference(jitsiWidget, true))
|
||||||
|
handleCheckWidgetAllowed(action)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleAcceptCall(action: RoomDetailAction.AcceptCall) {
|
private fun handleAcceptCall(action: RoomDetailAction.AcceptCall) {
|
||||||
callManager.getCallById(action.callId)?.also {
|
callManager.getCallById(action.callId)?.also {
|
||||||
_viewEvents.post(RoomDetailViewEvents.DisplayAndAcceptCall(it))
|
_viewEvents.post(RoomDetailViewEvents.DisplayAndAcceptCall(it))
|
||||||
|
|
|
@ -55,6 +55,13 @@ sealed class UnreadState {
|
||||||
data class HasUnread(val firstUnreadEventId: String) : UnreadState()
|
data class HasUnread(val firstUnreadEventId: String) : UnreadState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class JitsiState(
|
||||||
|
val hasJoined: Boolean = false,
|
||||||
|
// Not null if we have an active jitsi widget on the room
|
||||||
|
val confId: String? = null,
|
||||||
|
val widgetId: String? = null
|
||||||
|
)
|
||||||
|
|
||||||
data class RoomDetailViewState(
|
data class RoomDetailViewState(
|
||||||
val roomId: String,
|
val roomId: String,
|
||||||
val eventId: String?,
|
val eventId: String?,
|
||||||
|
@ -76,9 +83,7 @@ data class RoomDetailViewState(
|
||||||
val isAllowedToManageWidgets: Boolean = false,
|
val isAllowedToManageWidgets: Boolean = false,
|
||||||
val isAllowedToStartWebRTCCall: Boolean = true,
|
val isAllowedToStartWebRTCCall: Boolean = true,
|
||||||
val hasFailedSending: Boolean = false,
|
val hasFailedSending: Boolean = false,
|
||||||
val hasJoinedActiveJitsiConference: Boolean = false,
|
val jitsiState: JitsiState = JitsiState()
|
||||||
// Not null if we have an active jitsi widget on the room
|
|
||||||
val jitsiConfId: String? = null
|
|
||||||
) : MvRxState {
|
) : MvRxState {
|
||||||
|
|
||||||
constructor(args: RoomDetailArgs) : this(
|
constructor(args: RoomDetailArgs) : this(
|
||||||
|
@ -90,7 +95,7 @@ data class RoomDetailViewState(
|
||||||
|
|
||||||
fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
|
fun isWebRTCCallOptionAvailable() = (asyncRoomSummary.invoke()?.joinedMembersCount ?: 0) <= 2
|
||||||
|
|
||||||
fun hasActiveJitsiWidget() =jitsiConfId != null
|
fun hasActiveJitsiWidget() = jitsiState.confId != null
|
||||||
|
|
||||||
fun isDm() = asyncRoomSummary()?.isDirect == true
|
fun isDm() = asyncRoomSummary()?.isDirect == true
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ import im.vector.app.core.extensions.nextOrNull
|
||||||
import im.vector.app.core.extensions.prevOrNull
|
import im.vector.app.core.extensions.prevOrNull
|
||||||
import im.vector.app.core.resources.UserPreferencesProvider
|
import im.vector.app.core.resources.UserPreferencesProvider
|
||||||
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
import im.vector.app.features.call.webrtc.WebRtcCallManager
|
||||||
|
import im.vector.app.features.home.room.detail.JitsiState
|
||||||
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.RoomDetailViewState
|
import im.vector.app.features.home.room.detail.RoomDetailViewState
|
||||||
import im.vector.app.features.home.room.detail.UnreadState
|
import im.vector.app.features.home.room.detail.UnreadState
|
||||||
|
@ -51,6 +52,7 @@ import im.vector.app.features.home.room.detail.timeline.item.AbsMessageItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
import im.vector.app.features.home.room.detail.timeline.item.BasedMergedItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
|
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
|
import im.vector.app.features.home.room.detail.timeline.item.DaySeparatorItem_
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.item.ItemWithEvents
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
import im.vector.app.features.home.room.detail.timeline.item.MessageInformationData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptData
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
import im.vector.app.features.home.room.detail.timeline.item.ReadReceiptsItem
|
||||||
|
@ -87,6 +89,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
private val readReceiptsItemFactory: ReadReceiptsItemFactory
|
private val readReceiptsItemFactory: ReadReceiptsItemFactory
|
||||||
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
) : EpoxyController(backgroundHandler, backgroundHandler), Timeline.Listener, EpoxyController.Interceptor {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is a partial state of the RoomDetailViewState
|
||||||
|
*/
|
||||||
|
data class PartialState(
|
||||||
|
val unreadState: UnreadState = UnreadState.Unknown,
|
||||||
|
val highlightedEventId: String? = null,
|
||||||
|
val jitsiState: JitsiState = JitsiState()
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(state: RoomDetailViewState) : this(
|
||||||
|
unreadState = state.unreadState,
|
||||||
|
highlightedEventId = state.highlightedEventId,
|
||||||
|
jitsiState = state.jitsiState
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
interface Callback :
|
interface Callback :
|
||||||
BaseCallback,
|
BaseCallback,
|
||||||
ReactionPillCallback,
|
ReactionPillCallback,
|
||||||
|
@ -151,9 +169,8 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
private var inSubmitList: Boolean = false
|
private var inSubmitList: Boolean = false
|
||||||
private var hasReachedInvite: Boolean = false
|
private var hasReachedInvite: Boolean = false
|
||||||
private var hasUTD: Boolean = false
|
private var hasUTD: Boolean = false
|
||||||
private var unreadState: UnreadState = UnreadState.Unknown
|
|
||||||
private var positionOfReadMarker: Int? = null
|
private var positionOfReadMarker: Int? = null
|
||||||
private var eventIdToHighlight: String? = null
|
private var partialState: PartialState = PartialState()
|
||||||
|
|
||||||
var callback: Callback? = null
|
var callback: Callback? = null
|
||||||
var timeline: Timeline? = null
|
var timeline: Timeline? = null
|
||||||
|
@ -171,7 +188,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
// it's sent by the same user so we are sure we have up to date information.
|
// it's sent by the same user so we are sure we have up to date information.
|
||||||
val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
|
val invalidatedSenderId: String? = currentSnapshot.getOrNull(position)?.senderInfo?.userId
|
||||||
val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
|
val prevDisplayableEventIndex = currentSnapshot.subList(0, position).indexOfLast {
|
||||||
timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
|
timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
|
||||||
}
|
}
|
||||||
if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
|
if (prevDisplayableEventIndex != -1 && currentSnapshot[prevDisplayableEventIndex].senderInfo.userId == invalidatedSenderId) {
|
||||||
modelCache[prevDisplayableEventIndex] = null
|
modelCache[prevDisplayableEventIndex] = null
|
||||||
|
@ -223,29 +240,22 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
|
override fun intercept(models: MutableList<EpoxyModel<*>>) = synchronized(modelCache) {
|
||||||
interceptorHelper.intercept(models, unreadState, timeline, callback)
|
interceptorHelper.intercept(models, partialState.unreadState, timeline, callback)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun update(viewState: RoomDetailViewState) {
|
fun update(viewState: RoomDetailViewState) = synchronized(modelCache) {
|
||||||
var requestModelBuild = false
|
val newPartialState = PartialState(viewState)
|
||||||
if (eventIdToHighlight != viewState.highlightedEventId) {
|
if (partialState.highlightedEventId != newPartialState.highlightedEventId) {
|
||||||
// Clear cache to force a refresh
|
// Clear cache to force a refresh
|
||||||
synchronized(modelCache) {
|
for (i in 0 until modelCache.size) {
|
||||||
for (i in 0 until modelCache.size) {
|
if (modelCache[i]?.eventId == viewState.highlightedEventId
|
||||||
if (modelCache[i]?.eventId == viewState.highlightedEventId
|
|| modelCache[i]?.eventId == partialState.highlightedEventId) {
|
||||||
|| modelCache[i]?.eventId == eventIdToHighlight) {
|
modelCache[i] = null
|
||||||
modelCache[i] = null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
eventIdToHighlight = viewState.highlightedEventId
|
|
||||||
requestModelBuild = true
|
|
||||||
}
|
}
|
||||||
if (this.unreadState != viewState.unreadState) {
|
if (newPartialState != partialState) {
|
||||||
this.unreadState = viewState.unreadState
|
partialState = newPartialState
|
||||||
requestModelBuild = true
|
|
||||||
}
|
|
||||||
if (requestModelBuild) {
|
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -350,19 +360,19 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
val nextEvent = currentSnapshot.nextOrNull(position)
|
val nextEvent = currentSnapshot.nextOrNull(position)
|
||||||
val prevEvent = currentSnapshot.prevOrNull(position)
|
val prevEvent = currentSnapshot.prevOrNull(position)
|
||||||
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
|
val nextDisplayableEvent = currentSnapshot.subList(position + 1, currentSnapshot.size).firstOrNull {
|
||||||
timelineEventVisibilityHelper.shouldShowEvent(it, eventIdToHighlight)
|
timelineEventVisibilityHelper.shouldShowEvent(it, partialState.highlightedEventId)
|
||||||
}
|
}
|
||||||
val params = TimelineItemFactoryParams(
|
val params = TimelineItemFactoryParams(
|
||||||
event = event,
|
event = event,
|
||||||
prevEvent = prevEvent,
|
prevEvent = prevEvent,
|
||||||
nextEvent = nextEvent,
|
nextEvent = nextEvent,
|
||||||
nextDisplayableEvent = nextDisplayableEvent,
|
nextDisplayableEvent = nextDisplayableEvent,
|
||||||
highlightedEventId = eventIdToHighlight,
|
partialState = partialState,
|
||||||
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
|
lastSentEventIdWithoutReadReceipts = lastSentEventWithoutReadReceipts,
|
||||||
callback = callback
|
callback = callback
|
||||||
)
|
)
|
||||||
// Should be build if not cached or if model should be refreshed
|
// Should be build if not cached or if model should be refreshed
|
||||||
if (modelCache[position] == null || modelCache[position]?.shouldTriggerBuild == true) {
|
if (modelCache[position] == null || modelCache[position]?.isCacheable == false) {
|
||||||
modelCache[position] = buildCacheItem(params)
|
modelCache[position] = buildCacheItem(params)
|
||||||
}
|
}
|
||||||
val itemCachedData = modelCache[position] ?: return@forEach
|
val itemCachedData = modelCache[position] ?: return@forEach
|
||||||
|
@ -381,12 +391,13 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
it.id(event.localId)
|
it.id(event.localId)
|
||||||
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
|
it.setOnVisibilityStateChanged(TimelineEventVisibilityStateChangedListener(callback, event))
|
||||||
}
|
}
|
||||||
val shouldTriggerBuild = eventModel is AbsMessageItem && eventModel.attributes.informationData.sendStateDecoration == SendStateDecoration.SENT
|
val isCacheable = eventModel is ItemWithEvents && eventModel.isCacheable()
|
||||||
return CacheItemData(
|
return CacheItemData(
|
||||||
localId = event.localId,
|
localId = event.localId,
|
||||||
eventId = event.root.eventId,
|
eventId = event.root.eventId,
|
||||||
eventModel = eventModel,
|
eventModel = eventModel,
|
||||||
shouldTriggerBuild = shouldTriggerBuild)
|
isCacheable = isCacheable
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun CacheItemData.enrichWithModels(event: TimelineEvent,
|
private fun CacheItemData.enrichWithModels(event: TimelineEvent,
|
||||||
|
@ -399,7 +410,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
items = this@TimelineEventController.currentSnapshot,
|
items = this@TimelineEventController.currentSnapshot,
|
||||||
addDaySeparator = wantsDateSeparator,
|
addDaySeparator = wantsDateSeparator,
|
||||||
currentPosition = position,
|
currentPosition = position,
|
||||||
eventIdToHighlight = eventIdToHighlight,
|
eventIdToHighlight = partialState.highlightedEventId,
|
||||||
callback = callback
|
callback = callback
|
||||||
) {
|
) {
|
||||||
requestModelBuild()
|
requestModelBuild()
|
||||||
|
@ -428,7 +439,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// If the event is not shown, we go to the next one
|
// If the event is not shown, we go to the next one
|
||||||
if (!timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) {
|
if (!timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId)) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// If the event is sent by us, we update the holder with the eventId and stop the search
|
// If the event is sent by us, we update the holder with the eventId and stop the search
|
||||||
|
@ -451,7 +462,7 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
val currentReadReceipts = ArrayList(event.readReceipts).filter {
|
val currentReadReceipts = ArrayList(event.readReceipts).filter {
|
||||||
it.user.userId != session.myUserId
|
it.user.userId != session.myUserId
|
||||||
}
|
}
|
||||||
if (timelineEventVisibilityHelper.shouldShowEvent(event, eventIdToHighlight)) {
|
if (timelineEventVisibilityHelper.shouldShowEvent(event, partialState.highlightedEventId)) {
|
||||||
lastShownEventId = event.eventId
|
lastShownEventId = event.eventId
|
||||||
}
|
}
|
||||||
if (lastShownEventId == null) {
|
if (lastShownEventId == null) {
|
||||||
|
@ -533,6 +544,6 @@ class TimelineEventController @Inject constructor(private val dateFormatter: Vec
|
||||||
val eventModel: EpoxyModel<*>? = null,
|
val eventModel: EpoxyModel<*>? = null,
|
||||||
val mergedHeaderModel: BasedMergedItem<*>? = null,
|
val mergedHeaderModel: BasedMergedItem<*>? = null,
|
||||||
val formattedDayModel: DaySeparatorItem? = null,
|
val formattedDayModel: DaySeparatorItem? = null,
|
||||||
val shouldTriggerBuild: Boolean = false
|
val isCacheable: Boolean = true
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,13 @@ data class TimelineItemFactoryParams(
|
||||||
val prevEvent: TimelineEvent? = null,
|
val prevEvent: TimelineEvent? = null,
|
||||||
val nextEvent: TimelineEvent? = null,
|
val nextEvent: TimelineEvent? = null,
|
||||||
val nextDisplayableEvent: TimelineEvent? = null,
|
val nextDisplayableEvent: TimelineEvent? = null,
|
||||||
val highlightedEventId: String? = null,
|
val partialState: TimelineEventController.PartialState = TimelineEventController.PartialState(),
|
||||||
val lastSentEventIdWithoutReadReceipts: String? = null,
|
val lastSentEventIdWithoutReadReceipts: String? = null,
|
||||||
val callback: TimelineEventController.Callback? = null
|
val callback: TimelineEventController.Callback? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val highlightedEventId: String?
|
||||||
|
get() = partialState.highlightedEventId
|
||||||
|
|
||||||
val isHighlighted = highlightedEventId == event.eventId
|
val isHighlighted = highlightedEventId == event.eventId
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,28 +17,29 @@
|
||||||
package im.vector.app.features.home.room.detail.timeline.factory
|
package im.vector.app.features.home.room.detail.timeline.factory
|
||||||
|
|
||||||
import im.vector.app.ActiveSessionDataSource
|
import im.vector.app.ActiveSessionDataSource
|
||||||
import im.vector.app.R
|
|
||||||
import im.vector.app.core.epoxy.VectorEpoxyModel
|
import im.vector.app.core.epoxy.VectorEpoxyModel
|
||||||
import im.vector.app.core.resources.StringProvider
|
import im.vector.app.features.home.AvatarRenderer
|
||||||
|
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
import im.vector.app.features.home.room.detail.timeline.helper.AvatarSizeProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
import im.vector.app.features.home.room.detail.timeline.helper.MessageInformationDataFactory
|
||||||
import im.vector.app.features.home.room.detail.timeline.helper.MessageItemAttributesFactory
|
import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineItem
|
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem
|
||||||
import im.vector.app.features.home.room.detail.timeline.item.WidgetTileTimelineItem_
|
import im.vector.app.features.home.room.detail.timeline.item.CallTileTimelineItem_
|
||||||
import org.matrix.android.sdk.api.extensions.orFalse
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetContent
|
||||||
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
import org.matrix.android.sdk.api.session.widgets.model.WidgetType
|
||||||
|
import org.matrix.android.sdk.api.util.toMatrixItem
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class WidgetItemFactory @Inject constructor(
|
class WidgetItemFactory @Inject constructor(
|
||||||
private val sp: StringProvider,
|
|
||||||
private val messageItemAttributesFactory: MessageItemAttributesFactory,
|
|
||||||
private val informationDataFactory: MessageInformationDataFactory,
|
private val informationDataFactory: MessageInformationDataFactory,
|
||||||
private val noticeItemFactory: NoticeItemFactory,
|
private val noticeItemFactory: NoticeItemFactory,
|
||||||
private val avatarSizeProvider: AvatarSizeProvider,
|
private val avatarSizeProvider: AvatarSizeProvider,
|
||||||
private val activeSessionDataSource: ActiveSessionDataSource
|
private val messageColorProvider: MessageColorProvider,
|
||||||
|
private val avatarRenderer: AvatarRenderer,
|
||||||
|
private val activeSessionDataSource: ActiveSessionDataSource,
|
||||||
|
private val roomSummariesHolder: RoomSummariesHolder
|
||||||
) {
|
) {
|
||||||
private val currentUserId: String?
|
private val currentUserId: String?
|
||||||
get() = activeSessionDataSource.currentValue?.orNull()?.myUserId
|
get() = activeSessionDataSource.currentValue?.orNull()?.myUserId
|
||||||
|
@ -57,56 +58,41 @@ class WidgetItemFactory @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createJitsiItem(params: TimelineItemFactoryParams,
|
private fun createJitsiItem(params: TimelineItemFactoryParams, widgetContent: WidgetContent, prevWidgetContent: WidgetContent?): VectorEpoxyModel<*>? {
|
||||||
widgetContent: WidgetContent,
|
|
||||||
previousWidgetContent: WidgetContent?): VectorEpoxyModel<*> {
|
|
||||||
val timelineEvent = params.event
|
|
||||||
val informationData = informationDataFactory.create(params)
|
val informationData = informationDataFactory.create(params)
|
||||||
val attributes = messageItemAttributesFactory.create(null, informationData, params.callback)
|
val event = params.event
|
||||||
|
val roomId = event.roomId
|
||||||
val disambiguatedDisplayName = timelineEvent.senderInfo.disambiguatedDisplayName
|
val userOfInterest = roomSummariesHolder.get(roomId)?.toMatrixItem() ?: return null
|
||||||
val message = if (widgetContent.isActive()) {
|
val isActive = widgetContent.isActive()
|
||||||
val widgetName = widgetContent.getHumanName()
|
val callStatus = if (isActive && widgetContent.id == params.partialState.jitsiState.widgetId) {
|
||||||
if (previousWidgetContent?.isActive().orFalse()) {
|
if (params.partialState.jitsiState.hasJoined) {
|
||||||
// Widget has been modified
|
CallTileTimelineItem.CallStatus.IN_CALL
|
||||||
if (timelineEvent.root.isSentByCurrentUser()) {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_modified_by_you, widgetName)
|
|
||||||
} else {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_modified, disambiguatedDisplayName, widgetName)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Widget has been added
|
CallTileTimelineItem.CallStatus.INVITED
|
||||||
if (timelineEvent.root.isSentByCurrentUser()) {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_added_by_you, widgetName)
|
|
||||||
} else {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_added, disambiguatedDisplayName, widgetName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Widget has been removed
|
CallTileTimelineItem.CallStatus.ENDED
|
||||||
val widgetName = previousWidgetContent?.getHumanName()
|
|
||||||
if (timelineEvent.root.isSentByCurrentUser()) {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_removed_by_you, widgetName)
|
|
||||||
} else {
|
|
||||||
sp.getString(R.string.notice_widget_jitsi_removed, disambiguatedDisplayName, widgetName)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return WidgetTileTimelineItem_()
|
val fakeCallId = widgetContent.id ?: prevWidgetContent?.id ?: return null
|
||||||
.attributes(
|
val attributes = CallTileTimelineItem.Attributes(
|
||||||
WidgetTileTimelineItem.Attributes(
|
callId = fakeCallId,
|
||||||
title = message,
|
callKind = CallTileTimelineItem.CallKind.CONFERENCE,
|
||||||
drawableStart = R.drawable.ic_video,
|
callStatus = callStatus,
|
||||||
informationData = informationData,
|
informationData = informationData,
|
||||||
avatarRenderer = attributes.avatarRenderer,
|
avatarRenderer = avatarRenderer,
|
||||||
messageColorProvider = attributes.messageColorProvider,
|
messageColorProvider = messageColorProvider,
|
||||||
itemLongClickListener = attributes.itemLongClickListener,
|
itemClickListener = null,
|
||||||
itemClickListener = attributes.itemClickListener,
|
itemLongClickListener = null,
|
||||||
reactionPillCallback = attributes.reactionPillCallback,
|
reactionPillCallback = params.callback,
|
||||||
readReceiptsCallback = attributes.readReceiptsCallback,
|
readReceiptsCallback = params.callback,
|
||||||
emojiTypeFace = attributes.emojiTypeFace
|
userOfInterest = userOfInterest,
|
||||||
)
|
callback = params.callback,
|
||||||
)
|
isStillActive = isActive
|
||||||
|
)
|
||||||
|
return CallTileTimelineItem_()
|
||||||
|
.attributes(attributes)
|
||||||
|
.highlighted(params.isHighlighted)
|
||||||
.leftGuideline(avatarSizeProvider.leftGuideline)
|
.leftGuideline(avatarSizeProvider.leftGuideline)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 android.telecom.Conference
|
||||||
import com.airbnb.epoxy.EpoxyModel
|
import com.airbnb.epoxy.EpoxyModel
|
||||||
import com.airbnb.epoxy.VisibilityState
|
import com.airbnb.epoxy.VisibilityState
|
||||||
import im.vector.app.core.epoxy.LoadingItem_
|
import im.vector.app.core.epoxy.LoadingItem_
|
||||||
|
@ -113,11 +114,12 @@ class TimelineControllerInterceptorHelper(private val positionOfReadMarker: KMut
|
||||||
callIds: MutableSet<String>,
|
callIds: MutableSet<String>,
|
||||||
showHiddenEvents: Boolean
|
showHiddenEvents: Boolean
|
||||||
): Boolean {
|
): Boolean {
|
||||||
val callId = epoxyModel.attributes.callId
|
val attributes = epoxyModel.attributes
|
||||||
|
val callId = attributes.callId
|
||||||
// We should remove the call tile if we already have one for this call or
|
// We should remove the call tile if we already have one for this call or
|
||||||
// if this is an active call tile without an actual call (which can happen with permalink)
|
// if this is an active call tile without an actual call (which can happen with permalink)
|
||||||
val shouldRemoveCallItem = callIds.contains(callId)
|
val shouldRemoveCallItem = callIds.contains(callId)
|
||||||
|| (!callManager.getAdvertisedCalls().contains(callId) && epoxyModel.attributes.callStatus.isActive())
|
|| (!callManager.getAdvertisedCalls().contains(callId) && attributes.callStatus.isActive() && attributes.callKind != CallTileTimelineItem.CallKind.CONFERENCE)
|
||||||
val removed = shouldRemoveCallItem && !showHiddenEvents
|
val removed = shouldRemoveCallItem && !showHiddenEvents
|
||||||
if (removed) {
|
if (removed) {
|
||||||
remove()
|
remove()
|
||||||
|
|
|
@ -42,6 +42,10 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
|
||||||
override val baseAttributes: AbsBaseMessageItem.Attributes
|
override val baseAttributes: AbsBaseMessageItem.Attributes
|
||||||
get() = attributes
|
get() = attributes
|
||||||
|
|
||||||
|
override fun isCacheable(): Boolean {
|
||||||
|
return attributes.informationData.sendStateDecoration != SendStateDecoration.SENT
|
||||||
|
}
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var attributes: Attributes
|
lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
|
|
@ -37,7 +37,6 @@ import im.vector.app.features.home.room.detail.RoomDetailAction
|
||||||
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
import im.vector.app.features.home.room.detail.timeline.MessageColorProvider
|
||||||
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
import im.vector.app.features.home.room.detail.timeline.TimelineEventController
|
||||||
import org.matrix.android.sdk.api.util.MatrixItem
|
import org.matrix.android.sdk.api.util.MatrixItem
|
||||||
import timber.log.Timber
|
|
||||||
|
|
||||||
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
@EpoxyModelClass(layout = R.layout.item_timeline_event_base_state)
|
||||||
abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Holder>() {
|
abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Holder>() {
|
||||||
|
@ -45,6 +44,10 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
override val baseAttributes: AbsBaseMessageItem.Attributes
|
override val baseAttributes: AbsBaseMessageItem.Attributes
|
||||||
get() = attributes
|
get() = attributes
|
||||||
|
|
||||||
|
override fun isCacheable(): Boolean {
|
||||||
|
return attributes.callKind != CallKind.CONFERENCE
|
||||||
|
}
|
||||||
|
|
||||||
@EpoxyAttribute
|
@EpoxyAttribute
|
||||||
lateinit var attributes: Attributes
|
lateinit var attributes: Attributes
|
||||||
|
|
||||||
|
@ -64,61 +67,108 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
} else {
|
} else {
|
||||||
holder.callKindView.isVisible = false
|
holder.callKindView.isVisible = false
|
||||||
}
|
}
|
||||||
if (attributes.callStatus == CallStatus.INVITED && !attributes.informationData.sentByMe && attributes.isStillActive) {
|
when (attributes.callStatus) {
|
||||||
holder.acceptRejectViewGroup.isVisible = true
|
CallStatus.INVITED -> renderInvitedStatus(holder)
|
||||||
holder.acceptView.onClick {
|
CallStatus.IN_CALL -> renderInCallStatus(holder)
|
||||||
attributes.callback?.onTimelineItemAction(RoomDetailAction.AcceptCall(callId = attributes.callId))
|
CallStatus.REJECTED -> renderRejectedStatus(holder)
|
||||||
|
CallStatus.ENDED -> renderEndedStatus(holder)
|
||||||
|
}
|
||||||
|
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderEndedStatus(holder: Holder) {
|
||||||
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
|
holder.statusView.isVisible = true
|
||||||
|
holder.statusView.setText(R.string.call_tile_ended)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderRejectedStatus(holder: Holder) {
|
||||||
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
|
holder.statusView.isVisible = true
|
||||||
|
if (attributes.informationData.sentByMe) {
|
||||||
|
holder.statusView.setTextWithColoredPart(R.string.call_tile_you_declined, R.string.call_tile_call_back) {
|
||||||
|
val callbackAction = RoomDetailAction.StartCall(attributes.callKind == CallKind.VIDEO)
|
||||||
|
attributes.callback?.onTimelineItemAction(callbackAction)
|
||||||
}
|
}
|
||||||
holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.attr.colorOnPrimary)
|
} else {
|
||||||
holder.rejectView.onClick {
|
holder.statusView.text = holder.view.context.getString(R.string.call_tile_other_declined, attributes.userOfInterest.getBestName())
|
||||||
attributes.callback?.onTimelineItemAction(RoomDetailAction.EndCall)
|
}
|
||||||
}
|
}
|
||||||
holder.statusView.isVisible = false
|
|
||||||
when (attributes.callKind) {
|
private fun renderInCallStatus(holder: Holder) {
|
||||||
CallKind.CONFERENCE -> {
|
holder.acceptRejectViewGroup.isVisible = true
|
||||||
holder.rejectView.setText(R.string.ignore)
|
holder.acceptView.isVisible = false
|
||||||
holder.acceptView.setText(R.string.join)
|
when {
|
||||||
holder.acceptView.setLeftDrawable(R.drawable.ic_call_audio_small, R.attr.colorOnPrimary)
|
attributes.callKind == CallKind.CONFERENCE -> {
|
||||||
|
holder.statusView.isVisible = false
|
||||||
|
holder.rejectView.isVisible = true
|
||||||
|
holder.rejectView.setText(R.string.leave)
|
||||||
|
holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.attr.colorOnPrimary)
|
||||||
|
holder.rejectView.onClick {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.LeaveJitsiCall)
|
||||||
}
|
}
|
||||||
CallKind.AUDIO -> {
|
}
|
||||||
|
attributes.isStillActive -> {
|
||||||
|
holder.statusView.isVisible = false
|
||||||
|
holder.rejectView.isVisible = true
|
||||||
|
holder.rejectView.setText(R.string.call_notification_hangup)
|
||||||
|
holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.attr.colorOnPrimary)
|
||||||
|
holder.rejectView.onClick {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.EndCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
holder.statusView.isVisible = true
|
||||||
|
holder.statusView.setText(R.string.call_tile_in_call)
|
||||||
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderInvitedStatus(holder: Holder) {
|
||||||
|
when {
|
||||||
|
attributes.callKind == CallKind.CONFERENCE -> {
|
||||||
|
holder.statusView.isVisible = false
|
||||||
|
holder.acceptRejectViewGroup.isVisible = true
|
||||||
|
holder.acceptView.onClick {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.JoinJitsiCall)
|
||||||
|
}
|
||||||
|
holder.acceptView.isVisible = true
|
||||||
|
holder.rejectView.isVisible = false
|
||||||
|
holder.acceptView.setText(R.string.join)
|
||||||
|
holder.acceptView.setLeftDrawable(R.drawable.ic_call_video_small, R.attr.colorOnPrimary)
|
||||||
|
}
|
||||||
|
!attributes.informationData.sentByMe && attributes.isStillActive -> {
|
||||||
|
holder.acceptRejectViewGroup.isVisible = true
|
||||||
|
holder.acceptView.isVisible = true
|
||||||
|
holder.rejectView.isVisible = true
|
||||||
|
holder.acceptView.onClick {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.AcceptCall(callId = attributes.callId))
|
||||||
|
}
|
||||||
|
holder.rejectView.setLeftDrawable(R.drawable.ic_call_hangup, R.attr.colorOnPrimary)
|
||||||
|
holder.rejectView.onClick {
|
||||||
|
attributes.callback?.onTimelineItemAction(RoomDetailAction.EndCall)
|
||||||
|
}
|
||||||
|
holder.statusView.isVisible = false
|
||||||
|
if (attributes.callKind == CallKind.AUDIO) {
|
||||||
holder.rejectView.setText(R.string.call_notification_reject)
|
holder.rejectView.setText(R.string.call_notification_reject)
|
||||||
holder.acceptView.setText(R.string.call_notification_answer)
|
holder.acceptView.setText(R.string.call_notification_answer)
|
||||||
holder.acceptView.setLeftDrawable(R.drawable.ic_call_audio_small, R.attr.colorOnPrimary)
|
holder.acceptView.setLeftDrawable(R.drawable.ic_call_audio_small, R.attr.colorOnPrimary)
|
||||||
}
|
} else if (attributes.callKind == CallKind.VIDEO) {
|
||||||
CallKind.VIDEO -> {
|
|
||||||
holder.rejectView.setText(R.string.call_notification_reject)
|
holder.rejectView.setText(R.string.call_notification_reject)
|
||||||
holder.acceptView.setText(R.string.call_notification_answer)
|
holder.acceptView.setText(R.string.call_notification_answer)
|
||||||
holder.acceptView.setLeftDrawable(R.drawable.ic_call_video_small, R.attr.colorOnPrimary)
|
holder.acceptView.setLeftDrawable(R.drawable.ic_call_video_small, R.attr.colorOnPrimary)
|
||||||
}
|
}
|
||||||
else -> {
|
}
|
||||||
Timber.w("Shouldn't be in that state")
|
else -> {
|
||||||
|
holder.acceptRejectViewGroup.isVisible = false
|
||||||
|
holder.statusView.isVisible = true
|
||||||
|
if (attributes.informationData.sentByMe) {
|
||||||
|
holder.statusView.setText(R.string.call_tile_you_started_call)
|
||||||
|
} else {
|
||||||
|
holder.statusView.text = holder.view.context.getString(R.string.call_tile_other_started_call, attributes.userOfInterest.getBestName())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
holder.acceptRejectViewGroup.isVisible = false
|
|
||||||
holder.statusView.isVisible = true
|
|
||||||
}
|
|
||||||
holder.statusView.setCallStatus(attributes)
|
|
||||||
renderSendState(holder.view, null, holder.failedToSendIndicator)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TextView.setCallStatus(attributes: Attributes) {
|
|
||||||
when (attributes.callStatus) {
|
|
||||||
CallStatus.INVITED -> if (attributes.informationData.sentByMe) {
|
|
||||||
setText(R.string.call_tile_you_started_call)
|
|
||||||
} else {
|
|
||||||
text = context.getString(R.string.call_tile_other_started_call, attributes.userOfInterest.getBestName())
|
|
||||||
}
|
|
||||||
CallStatus.IN_CALL -> setText(R.string.call_tile_in_call)
|
|
||||||
CallStatus.REJECTED -> if (attributes.informationData.sentByMe) {
|
|
||||||
setTextWithColoredPart(R.string.call_tile_you_declined, R.string.call_tile_call_back) {
|
|
||||||
val callbackAction = RoomDetailAction.StartCall(attributes.callKind == CallKind.VIDEO)
|
|
||||||
attributes.callback?.onTimelineItemAction(callbackAction)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = context.getString(R.string.call_tile_other_declined, attributes.userOfInterest.getBestName())
|
|
||||||
}
|
|
||||||
CallStatus.ENDED -> setText(R.string.call_tile_ended)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +207,7 @@ abstract class CallTileTimelineItem : AbsBaseMessageItem<CallTileTimelineItem.Ho
|
||||||
enum class CallKind(@DrawableRes val icon: Int, @StringRes val title: Int) {
|
enum class CallKind(@DrawableRes val icon: Int, @StringRes val title: Int) {
|
||||||
VIDEO(R.drawable.ic_call_video_small, R.string.action_video_call),
|
VIDEO(R.drawable.ic_call_video_small, R.string.action_video_call),
|
||||||
AUDIO(R.drawable.ic_call_audio_small, R.string.action_voice_call),
|
AUDIO(R.drawable.ic_call_audio_small, R.string.action_voice_call),
|
||||||
CONFERENCE(R.drawable.ic_call_conference_small, R.string.conference_call_in_progress),
|
CONFERENCE(R.drawable.ic_call_video_small, R.string.action_video_call),
|
||||||
UNKNOWN(0, 0)
|
UNKNOWN(0, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,4 +26,9 @@ interface ItemWithEvents {
|
||||||
fun canAppendReadMarker(): Boolean = true
|
fun canAppendReadMarker(): Boolean = true
|
||||||
|
|
||||||
fun isVisible(): Boolean = true
|
fun isVisible(): Boolean = true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns false if you want epoxy controller to rebuild the event each time a built is triggered
|
||||||
|
*/
|
||||||
|
fun isCacheable(): Boolean = true
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue