WIP: Support using Element Call for voice calls in DMs
This commit is contained in:
@@ -26,9 +26,10 @@ sealed interface CallType : NodeInputs, Parcelable {
|
||||
data class RoomCall(
|
||||
val sessionId: SessionId,
|
||||
val roomId: RoomId,
|
||||
val voiceIntent: Boolean
|
||||
) : CallType {
|
||||
override fun toString(): String {
|
||||
return "RoomCall(sessionId=$sessionId, roomId=$roomId)"
|
||||
return "RoomCall(sessionId=$sessionId, roomId=$roomId, voiceIntent=$voiceIntent)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,8 @@ class RingingCallNotificationCreator(
|
||||
.setImportant(true)
|
||||
.build()
|
||||
|
||||
val answerIntent = IntentProvider.getPendingIntent(context, CallType.RoomCall(sessionId, roomId))
|
||||
// TODO
|
||||
val answerIntent = IntentProvider.getPendingIntent(context, CallType.RoomCall(sessionId, roomId, voiceIntent = false))
|
||||
val notificationData = CallNotificationData(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
|
||||
@@ -45,8 +45,9 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
||||
callType = CallType.RoomCall(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
),
|
||||
notificationData = notificationData,
|
||||
// TODO
|
||||
voiceIntent = false
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,6 +226,7 @@ class CallScreenPresenter(
|
||||
sessionId = inputs.sessionId,
|
||||
roomId = inputs.roomId,
|
||||
clientId = UUID.randomUUID().toString(),
|
||||
voiceOnly = inputs.voiceIntent,
|
||||
languageTag = languageTag,
|
||||
theme = theme,
|
||||
).getOrThrow()
|
||||
|
||||
@@ -112,7 +112,14 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
private fun onAnswer(notificationData: CallNotificationData) {
|
||||
elementCallEntryPoint.startCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId))
|
||||
elementCallEntryPoint.startCall(
|
||||
CallType.RoomCall(
|
||||
notificationData.sessionId,
|
||||
notificationData.roomId,
|
||||
// TODO
|
||||
voiceIntent = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCancel() {
|
||||
|
||||
@@ -146,6 +146,8 @@ class DefaultActiveCallManager(
|
||||
callType = CallType.RoomCall(
|
||||
sessionId = notificationData.sessionId,
|
||||
roomId = notificationData.roomId,
|
||||
// TODO
|
||||
voiceIntent = false,
|
||||
),
|
||||
callState = CallState.Ringing(notificationData),
|
||||
)
|
||||
|
||||
@@ -16,6 +16,7 @@ interface CallWidgetProvider {
|
||||
suspend fun getWidget(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
voiceOnly: Boolean,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
|
||||
@@ -32,6 +32,7 @@ class DefaultCallWidgetProvider(
|
||||
override suspend fun getWidget(
|
||||
sessionId: SessionId,
|
||||
roomId: RoomId,
|
||||
voiceOnly: Boolean,
|
||||
clientId: String,
|
||||
languageTag: String?,
|
||||
theme: String?,
|
||||
@@ -50,6 +51,7 @@ class DefaultCallWidgetProvider(
|
||||
baseUrl = baseUrl,
|
||||
encrypted = isEncrypted,
|
||||
direct = room.isDm(),
|
||||
voiceOnly = voiceOnly,
|
||||
hasActiveCall = roomInfo.hasRoomCall,
|
||||
)
|
||||
val callUrl = room.generateWidgetWebViewUrl(
|
||||
|
||||
@@ -272,10 +272,11 @@ class MessagesFlowNode(
|
||||
backstack.push(NavTarget.EditPoll(Timeline.Mode.Live, eventId))
|
||||
}
|
||||
|
||||
override fun navigateToRoomCall(roomId: RoomId) {
|
||||
override fun navigateToRoomCall(roomId: RoomId, voiceIntent: Boolean) {
|
||||
val callType = CallType.RoomCall(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
voiceIntent = voiceIntent
|
||||
)
|
||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||
elementCallEntryPoint.startCall(callType)
|
||||
@@ -488,10 +489,11 @@ class MessagesFlowNode(
|
||||
backstack.push(NavTarget.EditPoll(Timeline.Mode.Thread(navTarget.threadRootId), eventId))
|
||||
}
|
||||
|
||||
override fun navigateToRoomCall(roomId: RoomId) {
|
||||
override fun navigateToRoomCall(roomId: RoomId, voiceOnly: Boolean) {
|
||||
val callType = CallType.RoomCall(
|
||||
sessionId = sessionId,
|
||||
roomId = roomId,
|
||||
voiceIntent = voiceOnly
|
||||
)
|
||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||
elementCallEntryPoint.startCall(callType)
|
||||
|
||||
@@ -125,7 +125,7 @@ class MessagesNode(
|
||||
fun navigateToSendLocation()
|
||||
fun navigateToCreatePoll()
|
||||
fun navigateToEditPoll(eventId: EventId)
|
||||
fun navigateToRoomCall(roomId: RoomId)
|
||||
fun navigateToRoomCall(roomId: RoomId, voiceIntent: Boolean)
|
||||
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
|
||||
fun navigateToRoomDetails()
|
||||
fun navigateToPinnedMessagesList()
|
||||
@@ -279,7 +279,9 @@ class MessagesNode(
|
||||
},
|
||||
onSendLocationClick = callback::navigateToSendLocation,
|
||||
onCreatePollClick = callback::navigateToCreatePoll,
|
||||
onJoinCallClick = { callback.navigateToRoomCall(room.roomId) },
|
||||
onJoinCallClick = { voiceOnly ->
|
||||
callback.navigateToRoomCall(room.roomId, voiceOnly)
|
||||
},
|
||||
onViewAllPinnedMessagesClick = callback::navigateToPinnedMessagesList,
|
||||
modifier = modifier,
|
||||
knockRequestsBannerView = {
|
||||
|
||||
@@ -130,7 +130,7 @@ fun MessagesView(
|
||||
onLinkClick: (String, Boolean) -> Unit,
|
||||
onSendLocationClick: () -> Unit,
|
||||
onCreatePollClick: () -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceIntent: Boolean) -> Unit,
|
||||
onViewAllPinnedMessagesClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
forceJumpToBottomVisibility: Boolean = false,
|
||||
@@ -423,7 +423,7 @@ private fun MessagesViewContent(
|
||||
onMessageLongClick: (TimelineItem.Event) -> Unit,
|
||||
onSendLocationClick: () -> Unit,
|
||||
onCreatePollClick: () -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceIntent: Boolean) -> Unit,
|
||||
onViewAllPinnedMessagesClick: () -> Unit,
|
||||
forceJumpToBottomVisibility: Boolean,
|
||||
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||
|
||||
@@ -130,7 +130,7 @@ class ThreadedMessagesNode(
|
||||
fun navigateToSendLocation()
|
||||
fun navigateToCreatePoll()
|
||||
fun navigateToEditPoll(eventId: EventId)
|
||||
fun navigateToRoomCall(roomId: RoomId)
|
||||
fun navigateToRoomCall(roomId: RoomId, voiceOnly: Boolean)
|
||||
fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?)
|
||||
}
|
||||
|
||||
@@ -281,7 +281,9 @@ class ThreadedMessagesNode(
|
||||
},
|
||||
onSendLocationClick = callback::navigateToSendLocation,
|
||||
onCreatePollClick = callback::navigateToCreatePoll,
|
||||
onJoinCallClick = { callback.navigateToRoomCall(room.roomId) },
|
||||
onJoinCallClick = { voiceIntent ->
|
||||
callback.navigateToRoomCall(room.roomId, voiceIntent)
|
||||
},
|
||||
onViewAllPinnedMessagesClick = {},
|
||||
modifier = modifier,
|
||||
knockRequestsBannerView = {},
|
||||
|
||||
@@ -100,7 +100,7 @@ fun TimelineView(
|
||||
onReactionLongClick: (emoji: String, TimelineItem.Event) -> Unit,
|
||||
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
||||
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceIntent: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
lazyListState: LazyListState = rememberLazyListState(),
|
||||
forceJumpToBottomVisibility: Boolean = false,
|
||||
|
||||
@@ -10,6 +10,7 @@ package io.element.android.features.messages.impl.timeline.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.size
|
||||
@@ -35,7 +36,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||
@Composable
|
||||
internal fun CallMenuItem(
|
||||
roomCallState: RoomCallState,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceOnly: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (roomCallState) {
|
||||
@@ -52,7 +53,7 @@ internal fun CallMenuItem(
|
||||
is RoomCallState.OnGoing -> {
|
||||
OnGoingCallMenuItem(
|
||||
roomCallState = roomCallState,
|
||||
onJoinCallClick = onJoinCallClick,
|
||||
onJoinCallClick = { onJoinCallClick(roomCallState.isVoiceIntent) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
@@ -62,18 +63,30 @@ internal fun CallMenuItem(
|
||||
@Composable
|
||||
private fun StandByCallMenuItem(
|
||||
roomCallState: RoomCallState.StandBy,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceOnly: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
onClick = onJoinCallClick,
|
||||
enabled = roomCallState.canStartCall,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.VideoCallSolid(),
|
||||
contentDescription = stringResource(CommonStrings.a11y_start_call),
|
||||
)
|
||||
Row(modifier = modifier) {
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
onClick = { onJoinCallClick(true) },
|
||||
enabled = roomCallState.canStartCall,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.VoiceCallSolid(),
|
||||
contentDescription = stringResource(CommonStrings.a11y_start_call),
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
modifier = modifier,
|
||||
onClick = { onJoinCallClick(false) },
|
||||
enabled = roomCallState.canStartCall,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.VideoCallSolid(),
|
||||
contentDescription = stringResource(CommonStrings.a11y_start_call),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +109,11 @@ private fun OnGoingCallMenuItem(
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(20.dp),
|
||||
imageVector = CompoundIcons.VideoCallSolid(),
|
||||
imageVector = if (roomCallState.isVoiceIntent) {
|
||||
CompoundIcons.VoiceCallSolid()
|
||||
} else {
|
||||
CompoundIcons.VideoCallSolid()
|
||||
},
|
||||
contentDescription = null
|
||||
)
|
||||
Spacer(Modifier.width(8.dp))
|
||||
|
||||
@@ -46,7 +46,7 @@ internal fun TimelineItemCallNotifyView(
|
||||
event: TimelineItem.Event,
|
||||
roomCallState: RoomCallState,
|
||||
onLongClick: (TimelineItem.Event) -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceOnly: Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Row(
|
||||
|
||||
@@ -72,7 +72,7 @@ internal fun TimelineItemRow(
|
||||
onMoreReactionsClick: (TimelineItem.Event) -> Unit,
|
||||
onReadReceiptClick: (TimelineItem.Event) -> Unit,
|
||||
onSwipeToReply: (TimelineItem.Event) -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceIntent: Boolean) -> Unit,
|
||||
eventSink: (TimelineEvent.TimelineItemEvent) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
eventContentView: @Composable (TimelineItem.Event, Modifier, (ContentAvoidingLayoutData) -> Unit) -> Unit =
|
||||
|
||||
@@ -66,7 +66,7 @@ internal fun MessagesViewTopBar(
|
||||
dmUserIdentityState: IdentityState?,
|
||||
sharedHistoryIcon: SharedHistoryIcon,
|
||||
onRoomDetailsClick: () -> Unit,
|
||||
onJoinCallClick: () -> Unit,
|
||||
onJoinCallClick: (voiceIntent: Boolean) -> Unit,
|
||||
onBackClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
|
||||
@@ -18,10 +18,12 @@ sealed interface RoomCallState {
|
||||
|
||||
data class StandBy(
|
||||
val canStartCall: Boolean,
|
||||
// TODO: add is DM to know if should show the voice call option?
|
||||
) : RoomCallState
|
||||
|
||||
data class OnGoing(
|
||||
val canJoinCall: Boolean,
|
||||
val isVoiceIntent: Boolean,
|
||||
val isUserInTheCall: Boolean,
|
||||
val isUserLocallyInTheCall: Boolean,
|
||||
) : RoomCallState
|
||||
|
||||
@@ -17,6 +17,7 @@ open class RoomCallStateProvider : PreviewParameterProvider<RoomCallState> {
|
||||
anOngoingCallState(),
|
||||
anOngoingCallState(canJoinCall = false),
|
||||
anOngoingCallState(canJoinCall = true, isUserInTheCall = true),
|
||||
anOngoingCallState(canJoinCall = true, isVoiceIntent = true),
|
||||
RoomCallState.Unavailable,
|
||||
)
|
||||
}
|
||||
@@ -25,10 +26,12 @@ fun anOngoingCallState(
|
||||
canJoinCall: Boolean = true,
|
||||
isUserInTheCall: Boolean = false,
|
||||
isUserLocallyInTheCall: Boolean = isUserInTheCall,
|
||||
isVoiceIntent: Boolean = false,
|
||||
) = RoomCallState.OnGoing(
|
||||
canJoinCall = canJoinCall,
|
||||
isUserInTheCall = isUserInTheCall,
|
||||
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
||||
isVoiceIntent = isVoiceIntent
|
||||
)
|
||||
|
||||
fun aStandByCallState(
|
||||
|
||||
@@ -56,6 +56,7 @@ class RoomCallStatePresenter(
|
||||
canJoinCall = canJoinCall,
|
||||
isUserInTheCall = isUserInTheCall,
|
||||
isUserLocallyInTheCall = isUserLocallyInTheCall,
|
||||
isVoiceIntent = false // TODO
|
||||
)
|
||||
else -> RoomCallState.StandBy(canStartCall = canJoinCall)
|
||||
}
|
||||
|
||||
@@ -227,6 +227,8 @@ class RoomDetailsFlowNode(
|
||||
val inputs = CallType.RoomCall(
|
||||
sessionId = room.sessionId,
|
||||
roomId = room.roomId,
|
||||
// TODO
|
||||
voiceIntent = false
|
||||
)
|
||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||
elementCallEntryPoint.startCall(inputs)
|
||||
@@ -285,7 +287,14 @@ class RoomDetailsFlowNode(
|
||||
}
|
||||
|
||||
override fun startCall(dmRoomId: RoomId) {
|
||||
elementCallEntryPoint.startCall(CallType.RoomCall(roomId = dmRoomId, sessionId = room.sessionId))
|
||||
elementCallEntryPoint.startCall(
|
||||
CallType.RoomCall(
|
||||
roomId = dmRoomId,
|
||||
sessionId = room.sessionId,
|
||||
// TODO
|
||||
voiceIntent = false
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun startVerifyUserFlow(userId: UserId) {
|
||||
|
||||
@@ -84,7 +84,8 @@ class UserProfileFlowNode(
|
||||
}
|
||||
|
||||
override fun startCall(dmRoomId: RoomId) {
|
||||
elementCallEntryPoint.startCall(CallType.RoomCall(sessionId = sessionId, roomId = dmRoomId))
|
||||
// TODO
|
||||
elementCallEntryPoint.startCall(CallType.RoomCall(sessionId = sessionId, roomId = dmRoomId, voiceIntent = false))
|
||||
}
|
||||
|
||||
override fun startVerifyUserFlow(userId: UserId) {
|
||||
|
||||
@@ -16,6 +16,7 @@ interface CallWidgetSettingsProvider {
|
||||
widgetId: String = UUID.randomUUID().toString(),
|
||||
encrypted: Boolean,
|
||||
direct: Boolean,
|
||||
voiceOnly: Boolean,
|
||||
hasActiveCall: Boolean,
|
||||
): MatrixWidgetSettings
|
||||
}
|
||||
|
||||
@@ -30,7 +30,14 @@ class DefaultCallWidgetSettingsProvider(
|
||||
private val callAnalyticsCredentialsProvider: CallAnalyticCredentialsProvider,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : CallWidgetSettingsProvider {
|
||||
override suspend fun provide(baseUrl: String, widgetId: String, encrypted: Boolean, direct: Boolean, hasActiveCall: Boolean): MatrixWidgetSettings {
|
||||
override suspend fun provide(
|
||||
baseUrl: String,
|
||||
widgetId: String,
|
||||
encrypted: Boolean,
|
||||
direct: Boolean,
|
||||
voiceOnly: Boolean,
|
||||
hasActiveCall: Boolean
|
||||
): MatrixWidgetSettings {
|
||||
val isAnalyticsEnabled = analyticsService.userConsentFlow.first()
|
||||
val properties = VirtualElementCallWidgetProperties(
|
||||
elementCallUrl = baseUrl,
|
||||
@@ -47,14 +54,18 @@ class DefaultCallWidgetSettingsProvider(
|
||||
parentUrl = null,
|
||||
)
|
||||
val config = VirtualElementCallWidgetConfig(
|
||||
// TODO remove this once we have the next EC version
|
||||
preload = false,
|
||||
// TODO remove this once we have the next EC version
|
||||
skipLobby = null,
|
||||
// // TODO remove this once we have the next EC version
|
||||
// preload = false,
|
||||
// // TODO remove this once we have the next EC version
|
||||
// skipLobby = null,
|
||||
intent = when {
|
||||
direct && hasActiveCall -> CallIntent.JOIN_EXISTING_DM
|
||||
direct && hasActiveCall -> {
|
||||
if (voiceOnly) CallIntent.JOIN_EXISTING_DM_VOICE else CallIntent.JOIN_EXISTING_DM
|
||||
}
|
||||
hasActiveCall -> CallIntent.JOIN_EXISTING
|
||||
direct -> CallIntent.START_CALL_DM
|
||||
direct -> {
|
||||
if (voiceOnly) CallIntent.START_CALL_DM_VOICE else CallIntent.START_CALL_DM
|
||||
}
|
||||
else -> CallIntent.START_CALL
|
||||
}.also {
|
||||
Timber.d("Starting/joining call with intent: $it")
|
||||
|
||||
@@ -12,7 +12,14 @@ import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
|
||||
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
|
||||
|
||||
class FakeCallWidgetSettingsProvider(
|
||||
private val provideFn: (String, String, Boolean, Boolean, Boolean) -> MatrixWidgetSettings = { _, _, _, _, _ -> MatrixWidgetSettings("id", true, "url") }
|
||||
private val provideFn: (
|
||||
String,
|
||||
String,
|
||||
Boolean,
|
||||
Boolean,
|
||||
Boolean,
|
||||
Boolean
|
||||
) -> MatrixWidgetSettings = { _, _, _, _, _, _ -> MatrixWidgetSettings("id", true, "url") }
|
||||
) : CallWidgetSettingsProvider {
|
||||
val providedBaseUrls = mutableListOf<String>()
|
||||
|
||||
@@ -21,9 +28,10 @@ class FakeCallWidgetSettingsProvider(
|
||||
widgetId: String,
|
||||
encrypted: Boolean,
|
||||
direct: Boolean,
|
||||
voiceOnly: Boolean,
|
||||
hasActiveCall: Boolean
|
||||
): MatrixWidgetSettings {
|
||||
providedBaseUrls += baseUrl
|
||||
return provideFn(baseUrl, widgetId, encrypted, direct, hasActiveCall)
|
||||
return provideFn(baseUrl, widgetId, encrypted, direct, voiceOnly, hasActiveCall)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,7 +300,12 @@ class DefaultPushHandler(
|
||||
private suspend fun handleRingingCallEvent(notifiableEvent: NotifiableRingingCallEvent) {
|
||||
Timber.i("## handleInternal() : Incoming call.")
|
||||
elementCallEntryPoint.handleIncomingCall(
|
||||
callType = CallType.RoomCall(notifiableEvent.sessionId, notifiableEvent.roomId),
|
||||
callType = CallType.RoomCall(
|
||||
notifiableEvent.sessionId,
|
||||
notifiableEvent.roomId,
|
||||
// TODO
|
||||
voiceIntent = false
|
||||
),
|
||||
eventId = notifiableEvent.eventId,
|
||||
senderId = notifiableEvent.senderId,
|
||||
roomName = notifiableEvent.roomName,
|
||||
|
||||
Reference in New Issue
Block a user